You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
7.4 KiB
259 lines
7.4 KiB
import React, { useContext, useMemo, useRef, useState, useEffect } from 'react';
|
|
import { useHistory } from 'react-router-dom';
|
|
import { useSelector } from 'react-redux';
|
|
import PropTypes from 'prop-types';
|
|
import classnames from 'classnames';
|
|
import { getCurrentLocale } from '../../../ducks/metamask/metamask';
|
|
import { I18nContext } from '../../../contexts/i18n';
|
|
import { useEqualityCheck } from '../../../hooks/useEqualityCheck';
|
|
import Button from '../../ui/button';
|
|
import Popover from '../../ui/popover';
|
|
import Typography from '../../ui/typography';
|
|
import { updateViewedNotifications } from '../../../store/actions';
|
|
import { getTranslatedUINoficiations } from '../../../../shared/notifications';
|
|
import { getSortedNotificationsToShow } from '../../../selectors';
|
|
import {
|
|
BUILD_QUOTE_ROUTE,
|
|
ADVANCED_ROUTE,
|
|
EXPERIMENTAL_ROUTE,
|
|
} from '../../../helpers/constants/routes';
|
|
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
|
|
|
function getActionFunctionById(id, history) {
|
|
const actionFunctions = {
|
|
2: () => {
|
|
global.platform.openTab({
|
|
url:
|
|
'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021',
|
|
});
|
|
},
|
|
3: () => {
|
|
global.platform.openTab({
|
|
url: 'https://community.metamask.io/t/about-the-security-category/72',
|
|
});
|
|
},
|
|
4: () => {
|
|
updateViewedNotifications({ 4: true });
|
|
history.push(BUILD_QUOTE_ROUTE);
|
|
},
|
|
5: () => {
|
|
updateViewedNotifications({ 5: true });
|
|
global.platform.openTab({
|
|
url: 'https://metamask.zendesk.com/hc/en-us/articles/360060826432',
|
|
});
|
|
},
|
|
8: () => {
|
|
updateViewedNotifications({ 8: true });
|
|
history.push(ADVANCED_ROUTE);
|
|
},
|
|
10: () => {
|
|
updateViewedNotifications({ 10: true });
|
|
history.push(`${ADVANCED_ROUTE}#token-description`);
|
|
},
|
|
12: () => {
|
|
updateViewedNotifications({ 12: true });
|
|
history.push(EXPERIMENTAL_ROUTE);
|
|
},
|
|
};
|
|
|
|
return actionFunctions[id];
|
|
}
|
|
|
|
const renderDescription = (description) => {
|
|
if (!Array.isArray(description)) {
|
|
return (
|
|
<Typography variant={TYPOGRAPHY.Paragraph}>{description}</Typography>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{description.map((piece, index) => {
|
|
const isLast = index === description.length - 1;
|
|
return (
|
|
<Typography
|
|
key={`item-${index}`}
|
|
variant={TYPOGRAPHY.Paragraph}
|
|
boxProps={{ marginBottom: isLast ? 0 : 2 }}
|
|
>
|
|
{piece}
|
|
</Typography>
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const renderFirstNotification = (notification, idRefMap, history, isLast) => {
|
|
const { id, date, title, description, image, actionText } = notification;
|
|
const actionFunction = getActionFunctionById(id, history);
|
|
const imageComponent = image && (
|
|
<img
|
|
className="whats-new-popup__notification-image"
|
|
src={image.src}
|
|
height={image.height}
|
|
width={image.width}
|
|
/>
|
|
);
|
|
const placeImageBelowDescription = image?.placeImageBelowDescription;
|
|
return (
|
|
<div
|
|
className={classnames(
|
|
'whats-new-popup__notification whats-new-popup__first-notification',
|
|
{
|
|
'whats-new-popup__last-notification': isLast,
|
|
},
|
|
)}
|
|
key={`whats-new-popop-notification-${id}`}
|
|
>
|
|
{!placeImageBelowDescription && imageComponent}
|
|
<div className="whats-new-popup__notification-title">{title}</div>
|
|
<div className="whats-new-popup__description-and-date">
|
|
<div className="whats-new-popup__notification-description">
|
|
{renderDescription(description)}
|
|
</div>
|
|
<div className="whats-new-popup__notification-date">{date}</div>
|
|
</div>
|
|
{placeImageBelowDescription && imageComponent}
|
|
{actionText && (
|
|
<Button
|
|
type="secondary"
|
|
className="whats-new-popup__button"
|
|
onClick={actionFunction}
|
|
>
|
|
{actionText}
|
|
</Button>
|
|
)}
|
|
<div
|
|
className="whats-new-popup__intersection-observable"
|
|
ref={idRefMap[id]}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const renderSubsequentNotification = (
|
|
notification,
|
|
idRefMap,
|
|
history,
|
|
isLast,
|
|
) => {
|
|
const { id, date, title, description, actionText } = notification;
|
|
|
|
const actionFunction = getActionFunctionById(id, history);
|
|
return (
|
|
<div
|
|
className={classnames('whats-new-popup__notification', {
|
|
'whats-new-popup__last-notification': isLast,
|
|
})}
|
|
key={`whats-new-popop-notification-${id}`}
|
|
>
|
|
<div className="whats-new-popup__notification-title">{title}</div>
|
|
<div className="whats-new-popup__description-and-date">
|
|
<div className="whats-new-popup__notification-description">
|
|
{renderDescription(description)}
|
|
</div>
|
|
<div className="whats-new-popup__notification-date">{date}</div>
|
|
</div>
|
|
{actionText && (
|
|
<div className="whats-new-popup__link" onClick={actionFunction}>
|
|
{`${actionText} >`}
|
|
</div>
|
|
)}
|
|
<div
|
|
className="whats-new-popup__intersection-observable"
|
|
ref={idRefMap[id]}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default function WhatsNewPopup({ onClose }) {
|
|
const t = useContext(I18nContext);
|
|
const history = useHistory();
|
|
|
|
const notifications = useSelector(getSortedNotificationsToShow);
|
|
const locale = useSelector(getCurrentLocale);
|
|
|
|
const [seenNotifications, setSeenNotifications] = useState({});
|
|
|
|
const popoverRef = useRef();
|
|
|
|
const memoizedNotifications = useEqualityCheck(notifications);
|
|
const idRefMap = useMemo(
|
|
() =>
|
|
memoizedNotifications.reduce(
|
|
(_idRefMap, notification) => ({
|
|
..._idRefMap,
|
|
[notification.id]: React.createRef(),
|
|
}),
|
|
{},
|
|
),
|
|
[memoizedNotifications],
|
|
);
|
|
|
|
useEffect(() => {
|
|
const observer = new window.IntersectionObserver(
|
|
(entries, _observer) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
const [id, ref] = Object.entries(idRefMap).find(([_, _ref]) =>
|
|
_ref.current.isSameNode(entry.target),
|
|
);
|
|
|
|
setSeenNotifications((_seenNotifications) => ({
|
|
..._seenNotifications,
|
|
[id]: true,
|
|
}));
|
|
|
|
_observer.unobserve(ref.current);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
root: popoverRef.current,
|
|
threshold: 1.0,
|
|
},
|
|
);
|
|
|
|
Object.values(idRefMap).forEach((ref) => {
|
|
observer.observe(ref.current);
|
|
});
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}, [idRefMap, setSeenNotifications]);
|
|
|
|
return (
|
|
<Popover
|
|
className="whats-new-popup__popover"
|
|
title={t('whatsNew')}
|
|
onClose={() => {
|
|
updateViewedNotifications(seenNotifications);
|
|
onClose();
|
|
}}
|
|
popoverRef={popoverRef}
|
|
>
|
|
<div className="whats-new-popup__notifications">
|
|
{notifications.map(({ id }, index) => {
|
|
const notification = getTranslatedUINoficiations(t, locale)[id];
|
|
const isLast = index === notifications.length - 1;
|
|
// Display the swaps notification with full image
|
|
return index === 0 || id === 1
|
|
? renderFirstNotification(notification, idRefMap, history, isLast)
|
|
: renderSubsequentNotification(
|
|
notification,
|
|
idRefMap,
|
|
history,
|
|
isLast,
|
|
);
|
|
})}
|
|
</div>
|
|
</Popover>
|
|
);
|
|
}
|
|
|
|
WhatsNewPopup.propTypes = {
|
|
onClose: PropTypes.func.isRequired,
|
|
};
|
|
|