1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Whats new popup (#10583)

* Add 'What's New' notification popup

* Move selectors from shared/notifications into ui/ directory

* Use keys for localized message in whats new notifications objects, to ensure notifications will be translated.

* Remove unused swaps intro popup locale messages

* Fix keys of whats new notification locales

* Remove notifications messages and descriptions from comment in shared/notifications

* Move notifcationActionFunctions to shared/notifications and make it stateless

* Get notification data from constants instead of state in whats-new-popup

* Code cleanup

* Fix build quote reference to swapsEthToken, broken during rebase

* Rename notificationFilters to notificationToExclude to clarify its purpose

* Documentation for getSortedNotificationsToShow

* Move notification action functions from shared/ to whats-new-popup.js

* Stop setting swapsWelcomeMessageHasBeenShown to state in app-state controller

* Update e2e tests for whats new popup changes

* Updating migration files

* Addressing feedback part 1

* Addressing feedback part 2

* Remove unnecessary div in whats-new-popup

* Change getNotificationsToExclude to getNotificationsToInclude for use in the getSortedNotificationsToShow selector

* Delete intro-popup directory and test files

* Lint fix

* Add notifiction state to address-entry fixture

* Use two separate functions for rendering first and subsequent notifications in the whats-new-popup

* Ensure that string literals are passed to t for whats new popup text

* Update import-ui fixtures to include notificaiton controller state

* Remove unnecessary, accidental change confirm-approve

* Remove swaps notification in favour of mobile swaps as first notifcation and TBD 3rd notification

* Update whats-new-popup to use intersection observer api to detect if notification has been seen

* Add notifications to send-edit and threebox e2e test fixtures

* Update ui/app/selectors/selectors.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Update ui/app/selectors/selectors.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Clean up locale code for whats-new-popup notifications

* Disconnect observers in whats-new-popup when their callback is first called

* Add test case for migration 58 for when the AppStateController does not exist

* Rename popover components containerRef to popoverWrapRef

* Fix messages.json

* Update notification messages and images

* Rename popoverWrapRef -> popoverRef in whats-new-popup and popover.component

* Only create one observer, and only after images have loaded, in whats-new-popup

* Set width and height on whats-new-popup image, instead of setting state on img load

* Update ui/app/components/app/whats-new-popup/whats-new-popup.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Code clean up in whats new popup re: notification rendering and action functions

* Code cleanup in render notification functions of whats-new-popup

* Update ui/app/components/app/whats-new-popup/whats-new-popup.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* lint fix

* Update and localize  notification dates

* Clean up date code in shred/notifications/index.js

Co-authored-by: ryanml <ryanlanese@gmail.com>
Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
Dan J Miller 2021-04-28 14:21:41 -02:30 committed by GitHub
parent b6d8291dfc
commit b73f543b23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 634 additions and 521 deletions

View File

@ -1238,6 +1238,38 @@
"notEnoughGas": {
"message": "Not Enough Gas"
},
"notifications1Description": {
"message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
},
"notifications1Title": {
"message": "Swapping on mobile is here!",
"description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile."
},
"notifications2ActionText": {
"message": "Start survey",
"description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey."
},
"notifications2Description": {
"message": "Please share your experience in this 5 minute survey.",
"description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask."
},
"notifications2Title": {
"message": "Help improve MetaMask",
"description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better."
},
"notifications3ActionText": {
"message": "Read more",
"description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website."
},
"notifications3Description": {
"message": "Stay up to date on MetaMask security best practices and get the latest security tips from official MetaMask support.",
"description": "Description of a notification in the 'See What's New' popup. Describes the information they can get on security from the linked support page."
},
"notifications3Title": {
"message": "Stay secure",
"description": "Title for a notification in the 'See What's New' popup. Encourages users to consider security."
},
"ofTextNofM": {
"message": "of"
},
@ -1817,24 +1849,6 @@
"swapHighSlippageWarning": {
"message": "Slippage amount is very high. Make sure you know what you are doing!"
},
"swapIntroLearnMoreHeader": {
"message": "Want to learn more?"
},
"swapIntroLearnMoreLink": {
"message": "Learn more about MetaMask Swaps"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Liquidity sources include:"
},
"swapIntroPopupSubTitle": {
"message": "You can now swap tokens directly in your MetaMask wallet. MetaMask Swaps combines multiple decentralized exchange aggregators, professional market makers, and individual DEXs to ensure MetaMask users always get the best price with the lowest network fees."
},
"swapIntroPopupTitle": {
"message": "Token swapping is here!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Review our official contracts audit"
},
"swapLowSlippageError": {
"message": "Transaction may fail, max slippage too low."
},
@ -1961,9 +1975,6 @@
"swapSourceInfo": {
"message": "We search multiple liquidity sources (exchanges, aggregators and professional market makers) to find the best rates and lowest network fees."
},
"swapStartSwapping": {
"message": "Start swapping"
},
"swapSwapFrom": {
"message": "Swap from"
},
@ -2310,6 +2321,10 @@
"welcomeBack": {
"message": "Welcome Back!"
},
"whatsNew": {
"message": "What's new",
"description": "This is the title of a popup that gives users notifications about new features and updates to MetaMask."
},
"whatsThis": {
"message": "What's this?"
},

View File

@ -1579,24 +1579,6 @@
"swapHighSlippageWarning": {
"message": "La cantidad de deslizamiento es muy alta. ¡Asegúrate de saber lo que estás haciendo!"
},
"swapIntroLearnMoreHeader": {
"message": "¿Quiere aprender más?"
},
"swapIntroLearnMoreLink": {
"message": "Más información sobre los Intercambios MetaMask"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Las fuentes de liquidez incluyen:"
},
"swapIntroPopupSubTitle": {
"message": "Ahora puede intercambiar tokens directamente en su monedero MetaMask. Intercambios MetaMask combina múltiples agregadores de intercambio descentralizados, creadores de mercado profesionales y DEX individuales para garantizar que los usuarios de MetaMask siempre obtengan el mejor precio con las tarifas de red más bajas."
},
"swapIntroPopupTitle": {
"message": "¡El intercambio de tokens está aquí!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Revise nuestra auditoría de contratos oficiales"
},
"swapLowSlippageError": {
"message": "La transacción puede fallar, el deslizamiento máximo es demasiado bajo."
},
@ -1717,9 +1699,6 @@
"swapSourceInfo": {
"message": "Buscamos múltiples fuentes de liquidez (exchanges, agregadores y creadores de mercado profesionales) para encontrar las mejores tarifas y las tarifas de red más bajas."
},
"swapStartSwapping": {
"message": "Comenzar intercambio"
},
"swapSwapFrom": {
"message": "Intercambiar desde"
},

View File

@ -1579,24 +1579,6 @@
"swapHighSlippageWarning": {
"message": "La cantidad de deslizamiento es muy alta. ¡Asegúrate de saber lo que estás haciendo!"
},
"swapIntroLearnMoreHeader": {
"message": "¿Quiere aprender más?"
},
"swapIntroLearnMoreLink": {
"message": "Más información sobre los Intercambios MetaMask"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Las fuentes de liquidez incluyen:"
},
"swapIntroPopupSubTitle": {
"message": "Ahora puede intercambiar tokens directamente en su billetera MetaMask. Intercambios MetaMask combina múltiples agregadores de intercambio descentralizados, creadores de mercado profesionales y DEX individuales para garantizar que los usuarios de MetaMask siempre obtengan el mejor precio con las tarifas de red más bajas."
},
"swapIntroPopupTitle": {
"message": "¡El intercambio de tokens está aquí!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Revise nuestra auditoría de contratos oficiales"
},
"swapLowSlippageError": {
"message": "La transacción puede fallar, el deslizamiento máximo es demasiado bajo."
},
@ -1717,9 +1699,6 @@
"swapSourceInfo": {
"message": "Buscamos múltiples fuentes de liquidez (exchanges, agregadores y creadores de mercado profesionales) para encontrar las mejores tarifas y las tarifas de red más bajas."
},
"swapStartSwapping": {
"message": "Comenzar intercambio"
},
"swapSwapFrom": {
"message": "Intercambiar desde"
},

View File

@ -1567,24 +1567,6 @@
"swapHighSlippageWarning": {
"message": "स्लिपेज राशि बहुत अधिक है। सुनिश्चित करें कि आप जानते हैं कि आप क्या कर रहे हैं!"
},
"swapIntroLearnMoreHeader": {
"message": "अधिक सीखना चाहते हैं?"
},
"swapIntroLearnMoreLink": {
"message": "MetaMask स्वैप के बारे में अधिक जानें"
},
"swapIntroLiquiditySourcesLabel": {
"message": "चलनिधि स्रोतों में निम्न शामिल हैं:"
},
"swapIntroPopupSubTitle": {
"message": "अब आप अपने MetaMask वॉलेट में सीधे टोकन स्वैप कर सकते हैं। MetaMask स्वैप कई विकेंद्रीकृत विनिमय एग्रीगेटर, पेशेवर बाजार निर्माताओं और व्यक्तिगत DEX को जोड़ता है, ताकि MetaMask उपयोगकर्ताओं को हमेशा सबसे कम नेटवर्क शुल्क के साथ सबसे अच्छा मूल्य मिल सके।"
},
"swapIntroPopupTitle": {
"message": "टोकन स्वैपिंग यहाँ उपलब्ध है!"
},
"swapLearnMoreContractsAuditReview": {
"message": "हमारे आधिकारिक अनुबंधों के ऑडिट की समीक्षा करें"
},
"swapLowSlippageError": {
"message": "लेनदेन विफल हो सकता है, अधिकतम स्लिपेज बहुत कम हो सकता है।"
},
@ -1687,9 +1669,6 @@
"swapSourceInfo": {
"message": "हम सर्वोत्तम दरों और न्यूनतम नेटवर्क शुल्क का पता लगाने के लिए कई चलनिधि स्रोतों (एक्सचेंज, एग्रीगेटर और पेशेवर बाजार निर्माताओं) की खोज करते हैं।"
},
"swapStartSwapping": {
"message": "स्वैप करना शुरू करें"
},
"swapSwapFrom": {
"message": "इससे स्वैप करें"
},

View File

@ -1567,24 +1567,6 @@
"swapHighSlippageWarning": {
"message": "Jumlah slippage sangat tinggi. Pastikan Anda mengetahui yang Anda kerjakan!"
},
"swapIntroLearnMoreHeader": {
"message": "Ingin mempelajari selengkapnya?"
},
"swapIntroLearnMoreLink": {
"message": "Pelajari selengkapnya tentang Penukaran MetaMask"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Sumber likuiditas mencakup:"
},
"swapIntroPopupSubTitle": {
"message": "Sekarang, Anda bisa menukar token secara langsung di dompet MetaMask Anda. Penukaran MetaMask menggabungkan beberapa agregator penukaran terdesentralisasi, pembuat pasar profesional, dan DEX individu untuk memastikan pengguna MetaMask selalu mendapatkan harga terbaik dengan biaya jaringan terendah."
},
"swapIntroPopupTitle": {
"message": "Penukaran token ada di sini!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Tinjau audit kontrak resmi kami"
},
"swapLowSlippageError": {
"message": "Transaksi bisa gagal, slippage maks. terlalu rendah."
},
@ -1687,9 +1669,6 @@
"swapSourceInfo": {
"message": "Kami mencari beberapa sumber likuiditas (penukaran, agregator, dan pembuat pasar profesional) untuk menemukan tarif terbaik dan biaya jaringan terendah."
},
"swapStartSwapping": {
"message": "Mulai menukar"
},
"swapSwapFrom": {
"message": "Tukar dari"
},

View File

@ -1585,24 +1585,6 @@
"swapHighSlippageWarning": {
"message": "L'importo di slippage è molto alto. Assicurati di sapere cosa stai facendo!"
},
"swapIntroLearnMoreHeader": {
"message": "Vuoi sapere di più?"
},
"swapIntroLearnMoreLink": {
"message": "Scopri di più su MetaMask Swaps"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Sorgenti di liquidità incluse:"
},
"swapIntroPopupSubTitle": {
"message": "Adesso puoi scambiare token direttamente dal tuo portafgolio MetaMask. MetaMask Swaps combina vari siti di scambio decentralizzati, aggregatori e market maker professionisti per assicurare che gli utenti di MetaMask ottengano sempre il miglior prezzo con le tasse di rete minori."
},
"swapIntroPopupTitle": {
"message": "Lo scambio di token è qui!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Esamina l'audit ufficiale dei nostri smart contracts"
},
"swapLowSlippageError": {
"message": "La transazione può fallire, il massimo slippage è troppo basso."
},
@ -1729,9 +1711,6 @@
"swapSourceInfo": {
"message": "Cerchiamo sorgenti di liquidità multiple (siti di scambio, aggregatori, market maker professionisti) per trovare le tariffe migliori e le tasse di rete minori."
},
"swapStartSwapping": {
"message": "Inizia a scambiare"
},
"swapSwapFrom": {
"message": "Scambia da"
},

View File

@ -1579,24 +1579,6 @@
"swapHighSlippageWarning": {
"message": "非常に大きいスリッページ額です。本当に実行するか確認してください。"
},
"swapIntroLearnMoreHeader": {
"message": "詳細を表示しますか?"
},
"swapIntroLearnMoreLink": {
"message": "MetaMask Swapsの詳細"
},
"swapIntroLiquiditySourcesLabel": {
"message": "流動性ソースには以下が含まれます。"
},
"swapIntroPopupSubTitle": {
"message": "トークンをMetaMaskで直接スワップできるようになりました。MetaMask Swapsは、多数の分散型取引所アグリゲーター、専門のマーケットメーカー、DEX取引所を統合し、ユーザーは常に最低のネットワーク手数料、最適な価格で取引できます。"
},
"swapIntroPopupTitle": {
"message": "トークンのスワップはこちら!"
},
"swapLearnMoreContractsAuditReview": {
"message": "MetaSwapのコントラクト監査のレビュー"
},
"swapLowSlippageError": {
"message": "トランザクションが失敗する可能性があります。最大スリッページが少なすぎます。"
},
@ -1717,9 +1699,6 @@
"swapSourceInfo": {
"message": "最良のレートと最小のネットワーク手数料を探すため、複数の流動性ソース(取引所、アグリゲーター、専門のマーケットメーカー)を検索します。"
},
"swapStartSwapping": {
"message": "スワップの開始"
},
"swapSwapFrom": {
"message": "スワップ元"
},

View File

@ -1567,24 +1567,6 @@
"swapHighSlippageWarning": {
"message": "슬리패지 금액이 아주 큽니다. 현재 어떤 작업을 하고 있는지 확인하세요!"
},
"swapIntroLearnMoreHeader": {
"message": "자세한 정보를 확인하고 싶으신가요?"
},
"swapIntroLearnMoreLink": {
"message": "MetaMask Swaps에 대해 자세히 알아보기"
},
"swapIntroLiquiditySourcesLabel": {
"message": "다음을 포함한 유동성 소스:"
},
"swapIntroPopupSubTitle": {
"message": "이제 MetaMask 지갑에서 토큰을 바로 스왑할 수 있습니다. MetaMask Swaps는 다양한 분산형 교환 애그리게이터, 투자전문기관, 개별 DEX를 결합하여 MetaMask 사용자가 언제든 최저 네트워크 요금으로 최상의 가격을 얻을 수 있게 합니다."
},
"swapIntroPopupTitle": {
"message": "토큰 스왑은 여기서 진행됩니다!"
},
"swapLearnMoreContractsAuditReview": {
"message": "당사의 공식 계약 감사 검토"
},
"swapLowSlippageError": {
"message": "거래가 실패할 수도 있습니다. 최대 슬리패지가 너무 낮습니다."
},
@ -1687,9 +1669,6 @@
"swapSourceInfo": {
"message": "당사에서는 여러 유동성 소스(교환, 애그리게이터, 투자전문기관)를 검색하여 최상의 요율과 최저 네트워크 요금을 찾습니다."
},
"swapStartSwapping": {
"message": "스왑 시작"
},
"swapSwapFrom": {
"message": "다음에서 스왑"
},

View File

@ -1567,24 +1567,6 @@
"swapHighSlippageWarning": {
"message": "Величина проскальзывания очень велика. Убедитесь, что вы знаете, что делаете!"
},
"swapIntroLearnMoreHeader": {
"message": "Хотите узнать больше?"
},
"swapIntroLearnMoreLink": {
"message": "Подробнее о свопах MetaMask"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Источники ликвидности включают:"
},
"swapIntroPopupSubTitle": {
"message": "Теперь вы можете обменивать токены прямо в кошельке MetaMask. MetaMask Swaps объединяет несколько децентрализованных агрегаторов обменов, профессиональных торговцев и отдельные DEX, чтобы пользователи MetaMask всегда получали лучшую цену с минимальными комиссиями сети."
},
"swapIntroPopupTitle": {
"message": "Обмен токенов здесь!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Ознакомьтесь с нашим официальным аудитом контрактов"
},
"swapLowSlippageError": {
"message": "Транзакции могут завершиться неудачей, максимальное проскальзывание слишком мало."
},
@ -1687,9 +1669,6 @@
"swapSourceInfo": {
"message": "Мы ищем несколько источников ликвидности (биржи, агрегаторы и профессиональные продавцы), чтобы найти лучшие цены и самые низкие сетевые комиссии."
},
"swapStartSwapping": {
"message": "Начать обмен"
},
"swapSwapFrom": {
"message": "Своп с"
},

View File

@ -1564,24 +1564,6 @@
"swapHighSlippageWarning": {
"message": "Sobrang laki ng halaga ng slippage. Tiyaking alam mo ang ginagawa mo!"
},
"swapIntroLearnMoreHeader": {
"message": "Gusto mo bang matuto pa?"
},
"swapIntroLearnMoreLink": {
"message": "Matuto pa tungkol sa MetaMask Swaps"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Kasama sa mga pinagkunan ng liquidity ang:"
},
"swapIntroPopupSubTitle": {
"message": "Puwede mo nang direktang i-swap ang mga token sa iyong MetaMask wallet. Pinagsasama-sama ng MetaMask Swaps ang maraming decentralized exchange aggregator, propesyonal na market maker, at indibidwal na DEX para matiyak na makukuha palagi ng mga user ng MetaMask ang pinakasulit na presyo nang may pinakamababang bayarin sa network."
},
"swapIntroPopupTitle": {
"message": "Ito na ang pag-swap ng token!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Suriin ang aming audit ng mga opisyal na kontrata"
},
"swapLowSlippageError": {
"message": "Maaaring hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage."
},
@ -1684,9 +1666,6 @@
"swapSourceInfo": {
"message": "Naghahanap kami ng maraming pinagkukunan ng liquidity (mga exchange, aggregator at propesyonal na market maker) para mahanap ang mga pinakasulit na rate at pinakamababang bayarin sa network."
},
"swapStartSwapping": {
"message": "Simulang mag-swap"
},
"swapSwapFrom": {
"message": "Ipalit mula sa"
},

View File

@ -1567,24 +1567,6 @@
"swapHighSlippageWarning": {
"message": "Số tiền trượt giá rất cao. Hãy chắc chắn rằng bạn hiểu những gì mình đang làm!"
},
"swapIntroLearnMoreHeader": {
"message": "Bạn muốn tìm hiểu thêm?"
},
"swapIntroLearnMoreLink": {
"message": "Tìm hiểu thêm về MetaMask Swaps"
},
"swapIntroLiquiditySourcesLabel": {
"message": "Các nguồn thanh khoản bao gồm:"
},
"swapIntroPopupSubTitle": {
"message": "Giờ đây bạn có thể hoán đổi token ngay trong ví MetaMask của mình. MetaMask Swaps quy tụ nhiều trình tổng hợp sàn giao dịch phi tập trung, các nhà tạo lập thị trường chuyên nghiệp và các sàn giao dịch phi tập trung dành cho cá nhân nhằm đảm bảo người dùng MetaMask luôn nhận được mức giá tốt nhất với phí mạng thấp nhất."
},
"swapIntroPopupTitle": {
"message": "Tính năng hoán đổi token đã sẵn sàng!"
},
"swapLearnMoreContractsAuditReview": {
"message": "Xem xét quy trình kiểm tra hợp đồng chính thức của chúng tôi"
},
"swapLowSlippageError": {
"message": "Giao dịch có thể không thành công, mức trượt giá tối đa quá thấp."
},
@ -1687,9 +1669,6 @@
"swapSourceInfo": {
"message": "Chúng tôi tìm kiếm nhiều nguồn thanh khoản (các sàn giao dịch, trình tổng hợp và nhà tạo lập thị trường) để tìm được mức tỷ lệ tốt nhất và phí mạng thấp nhất."
},
"swapStartSwapping": {
"message": "Bắt đầu hoán đổi"
},
"swapSwapFrom": {
"message": "Hoán đổi từ"
},

View File

@ -1579,24 +1579,6 @@
"swapHighSlippageWarning": {
"message": "滑点数量非常大。确保您知道您的操作!"
},
"swapIntroLearnMoreHeader": {
"message": "想了解更多信息?"
},
"swapIntroLearnMoreLink": {
"message": "了解更多关于 MetaMask Swap兑换"
},
"swapIntroLiquiditySourcesLabel": {
"message": "流动资金来源包括:"
},
"swapIntroPopupSubTitle": {
"message": "现在您可以直接在 MetaMask 钱包中兑换代币。MetaMask Swaps兑换结合了多个去中心化交易所聚合商、专业做市商和个人 DEX确保 MetaMask 用户始终以最低的网络费用获得最佳价格。"
},
"swapIntroPopupTitle": {
"message": "代币兑换来了!"
},
"swapLearnMoreContractsAuditReview": {
"message": "查看我们的官方合约审计"
},
"swapLowSlippageError": {
"message": "交易可能失败,最大滑点过低。"
},
@ -1717,9 +1699,6 @@
"swapSourceInfo": {
"message": "我们搜索多个流动性来源(交易所、聚合商和专业做市商),以找到最好的利率和最低的网络手续费。"
},
"swapStartSwapping": {
"message": "开始兑换"
},
"swapSwapFrom": {
"message": "兑换自"
},

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -22,7 +22,6 @@ export default class AppStateController extends EventEmitter {
this.store = new ObservableStore({
timeoutMinutes: 0,
connectedStatusPopoverHasBeenShown: true,
swapsWelcomeMessageHasBeenShown: false,
defaultHomeActiveTabName: null,
...initState,
});
@ -112,15 +111,6 @@ export default class AppStateController extends EventEmitter {
});
}
/**
* Record that the user has seen the swap screen welcome message
*/
setSwapsWelcomeMessageHasBeenShown() {
this.store.updateState({
swapsWelcomeMessageHasBeenShown: true,
});
}
/**
* Sets the last active time to the current time
* @returns {void}

View File

@ -22,9 +22,12 @@ import {
ApprovalController,
CurrencyRateController,
PhishingController,
NotificationController,
} from '@metamask/controllers';
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import { MAINNET_CHAIN_ID } from '../../shared/constants/network';
import { UI_NOTIFICATIONS } from '../../shared/notifications';
import ComposableObservableStore from './lib/ComposableObservableStore';
import AccountTracker from './lib/account-tracker';
import createLoggerMiddleware from './lib/createLoggerMiddleware';
@ -160,6 +163,11 @@ export default class MetamaskController extends EventEmitter {
this.phishingController = new PhishingController();
this.notificationController = new NotificationController(
{ allNotifications: UI_NOTIFICATIONS },
initState.NotificationController,
);
// now we can initialize the RPC provider, which other controllers require
this.initializeProvider();
this.provider = this.networkController.getProviderAndBlockTracker().provider;
@ -426,6 +434,7 @@ export default class MetamaskController extends EventEmitter {
PermissionsController: this.permissionsController.permissions,
PermissionsMetadata: this.permissionsController.store,
ThreeBoxController: this.threeBoxController.store,
NotificationController: this.notificationController,
});
this.memStore = new ComposableObservableStore(null, {
@ -454,6 +463,7 @@ export default class MetamaskController extends EventEmitter {
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
NotificationController: this.notificationController,
});
this.memStore.subscribe(this.sendUpdate.bind(this));
@ -736,10 +746,6 @@ export default class MetamaskController extends EventEmitter {
this.appStateController.setConnectedStatusPopoverHasBeenShown,
this.appStateController,
),
setSwapsWelcomeMessageHasBeenShown: nodeify(
this.appStateController.setSwapsWelcomeMessageHasBeenShown,
this.appStateController,
),
// EnsController
tryReverseResolveAddress: nodeify(
@ -946,6 +952,12 @@ export default class MetamaskController extends EventEmitter {
approvalController.reject,
approvalController,
),
// Notifications
updateViewedNotifications: nodeify(
this.notificationController.updateViewed,
this.notificationController,
),
};
}

View File

@ -0,0 +1,23 @@
import { cloneDeep } from 'lodash';
const version = 58;
/**
* Deletes the swapsWelcomeMessageHasBeenShown property from state
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
versionedData.data = transformState(state);
return versionedData;
},
};
function transformState(state) {
delete state.AppStateController?.swapsWelcomeMessageHasBeenShown;
return state;
}

View File

@ -0,0 +1,46 @@
import { strict as assert } from 'assert';
import migration58 from './058';
describe('migration #58', function () {
it('should update the version metadata', async function () {
const oldStorage = {
meta: {
version: 57,
},
data: {},
};
const newStorage = await migration58.migrate(oldStorage);
assert.deepEqual(newStorage.meta, {
version: 58,
});
});
describe('deleting swapsWelcomeMessageHasBeenShown', function () {
it('should delete the swapsWelcomeMessageHasBeenShown property', async function () {
const oldStorage = {
meta: {},
data: {
AppStateController: {
swapsWelcomeMessageHasBeenShown: false,
bar: 'baz',
},
foo: 'bar',
},
};
const newStorage = await migration58.migrate(oldStorage);
assert.deepEqual(newStorage.data.AppStateController, { bar: 'baz' });
});
it('should not modify state if the AppStateController does not exist', async function () {
const oldStorage = {
meta: {},
data: {
foo: 'bar',
},
};
const newStorage = await migration58.migrate(oldStorage);
assert.deepEqual(newStorage.data, oldStorage.data);
});
});
});

View File

@ -62,6 +62,7 @@ const migrations = [
require('./055').default,
require('./056').default,
require('./057').default,
require('./058').default,
];
export default migrations;

View File

@ -0,0 +1,51 @@
// Messages and descriptions for these locale keys are in app/_locales/en/messages.json
export const UI_NOTIFICATIONS = {
1: {
id: 1,
date: '2021-03-17',
image: {
src: 'images/mobile-link-qr.svg',
height: '270px',
width: '270px',
},
},
2: {
id: 2,
date: '2020-08-31',
},
3: {
id: 3,
date: '2021-03-8',
},
};
export const getTranslatedUINoficiations = (t, locale) => {
return {
1: {
...UI_NOTIFICATIONS[1],
title: t('notifications1Title'),
description: t('notifications1Description'),
date: new Intl.DateTimeFormat(locale).format(
new Date(UI_NOTIFICATIONS[1].date),
),
},
2: {
...UI_NOTIFICATIONS[2],
title: t('notifications2Title'),
description: t('notifications2Description'),
actionText: t('notifications2ActionText'),
date: new Intl.DateTimeFormat(locale).format(
new Date(UI_NOTIFICATIONS[2].date),
),
},
3: {
...UI_NOTIFICATIONS[3],
title: t('notifications3Title'),
description: t('notifications3Description'),
actionText: t('notifications3ActionText'),
date: new Intl.DateTimeFormat(locale).format(
new Date(UI_NOTIFICATIONS[3].date),
),
},
};
};

View File

@ -50,6 +50,19 @@
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -1,8 +1,7 @@
{
"data": {
"AppStateController": {
"connectedStatusPopoverHasBeenShown": false,
"swapsWelcomeMessageHasBeenShown": true
"connectedStatusPopoverHasBeenShown": false
},
"CachedBalancesController": {
"cachedBalances": {
@ -41,6 +40,19 @@
},
"network": "1337"
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -91,6 +91,19 @@
},
"network": "1337"
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"CurrencyController": {
"conversionDate": 1618940438.187,
"conversionRate": 2254.54,

View File

@ -1,8 +1,7 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null,
"swapsWelcomeMessageHasBeenShown": true
"mkrMigrationReminderTimestamp": null
},
"CachedBalancesController": {
"cachedBalances": {
@ -37,6 +36,19 @@
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -1,8 +1,7 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null,
"swapsWelcomeMessageHasBeenShown": true
"mkrMigrationReminderTimestamp": null
},
"CachedBalancesController": {
"cachedBalances": {
@ -37,6 +36,19 @@
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -1,8 +1,7 @@
{
"data": {
"AppStateController": {
"connectedStatusPopoverHasBeenShown": false,
"swapsWelcomeMessageHasBeenShown": true
"connectedStatusPopoverHasBeenShown": false
},
"CachedBalancesController": {
"cachedBalances": {
@ -41,6 +40,19 @@
},
"network": "1337"
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -37,6 +37,19 @@
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false

View File

@ -47,6 +47,19 @@
},
"network": "1337"
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"2": {
"isShown": true
},
"3": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": true

View File

@ -137,6 +137,24 @@ describe('MetaMask', function () {
});
});
describe("Close the what's new popup", function () {
it("should show the what's new popover", async function () {
const popoverTitle = await driver.findElement(
'.popover-header__title h2',
);
assert.equal(await popoverTitle.getText(), "What's new");
});
it("should close the what's new popup", async function () {
const popover = await driver.findElement('.popover-container');
await driver.clickElement('[data-testid="popover-close"]');
await popover.waitForElementState('hidden');
});
});
describe('Show account information', function () {
it('shows the QR code for the account', async function () {
await driver.clickElement('[data-testid="account-options-menu-button"]');

View File

@ -60,6 +60,11 @@ describe('Metamask Import UI', function () {
tag: 'button',
});
// close the what's new popup
const popover = await driver.findElement('.popover-container');
await driver.clickElement('[data-testid="popover-close"]');
await popover.waitForElementState('hidden');
// Show account information
await driver.clickElement(
'[data-testid="account-options-menu-button"]',

View File

@ -65,6 +65,13 @@ describe('Incremental Security', function () {
tag: 'button',
});
// closes the what's new popup
const popover = await driver.findElement('.popover-container');
await driver.clickElement('[data-testid="popover-close"]');
await popover.waitForElementState('hidden');
await driver.clickElement(
'[data-testid="account-options-menu-button"]',
);

View File

@ -36,3 +36,4 @@
@import 'transaction-list/index';
@import 'transaction-status/index';
@import 'wallet-overview/index';
@import 'whats-new-popup/index';

View File

@ -0,0 +1 @@
export { default } from './whats-new-popup';

View File

@ -0,0 +1,51 @@
.whats-new-popup {
&__notifications {
display: flex;
flex-direction: column;
align-items: center;
}
&__notification,
&__first-notification {
display: flex;
flex-direction: column;
align-items: left;
margin: 0 24px 24px 24px;
border-bottom: 1px solid $Grey-100;
}
&__notification-image {
margin-bottom: 16px;
}
&__description-and-date {
margin-bottom: 16px;
}
&__notification-date {
color: $Grey-500;
}
&__button {
margin-right: auto;
}
&__button,
&__link {
margin-bottom: 24px;
}
&__link {
@include H6;
color: $Blue-500;
cursor: pointer;
}
&__notification-title {
@include H4;
font-weight: bold;
margin-bottom: 8px;
}
}

View File

@ -0,0 +1,179 @@
import React, { useContext, useMemo, useRef, useState, useEffect } from 'react';
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 { updateViewedNotifications } from '../../../store/actions';
import { getTranslatedUINoficiations } from '../../../../../shared/notifications';
import { getSortedNotificationsToShow } from '../../../selectors';
function getActionFunctionById(id) {
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',
});
},
};
return actionFunctions[id];
}
const renderFirstNotification = (notification, idRefMap) => {
const { id, date, title, description, image, actionText } = notification;
const actionFunction = getActionFunctionById(id);
return (
<div
className={classnames(
'whats-new-popup__notification whats-new-popup__first-notification',
)}
key={`whats-new-popop-notificatiion-${id}`}
ref={idRefMap[id]}
>
{image && (
<img
className="whats-new-popup__notification-image"
src={image.src}
height={image.height}
width={image.width}
/>
)}
<div className="whats-new-popup__notification-title">{title}</div>
<div className="whats-new-popup__description-and-date">
<div className="whats-new-popup__notification-description">
{description}
</div>
<div className="whats-new-popup__notification-date">{date}</div>
</div>
{actionText && (
<Button
type="secondary"
className="whats-new-popup__button"
rounded
onClick={actionFunction}
>
{actionText}
</Button>
)}
</div>
);
};
const renderSubsequentNotification = (notification, idRefMap) => {
const { id, date, title, description, actionText } = notification;
const actionFunction = getActionFunctionById(id);
return (
<div
className={classnames('whats-new-popup__notification')}
key={`whats-new-popop-notificatiion-${id}`}
ref={idRefMap[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">
{description}
</div>
<div className="whats-new-popup__notification-date">{date}</div>
</div>
{actionText && (
<div className="whats-new-popup__link" onClick={actionFunction}>
{actionText}
</div>
)}
</div>
);
};
export default function WhatsNewPopup({ onClose }) {
const t = useContext(I18nContext);
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}
mediumHeight
>
<div className="whats-new-popup__notifications">
{notifications.map(({ id }, index) => {
const notification = getTranslatedUINoficiations(t, locale)[id];
return index === 0
? renderFirstNotification(notification, idRefMap)
: renderSubsequentNotification(notification, idRefMap);
})}
</div>
</Popover>
);
}
WhatsNewPopup.propTypes = {
onClose: PropTypes.func.isRequired,
};

View File

@ -18,6 +18,10 @@
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.25);
border-radius: 10px;
background: white;
&--medium-height {
max-height: 600px;
}
}
&-header {

View File

@ -13,9 +13,11 @@ const Popover = ({
onBack,
onClose,
className,
mediumHeight,
contentClassName,
showArrow,
CustomBackground,
popoverRef,
}) => {
const t = useI18nContext();
return (
@ -25,7 +27,12 @@ const Popover = ({
) : (
<div className="popover-bg" onClick={onClose} />
)}
<section className={classnames('popover-wrap', className)}>
<section
className={classnames('popover-wrap', className, {
'popover-wrap--medium-height': mediumHeight,
})}
ref={popoverRef}
>
{showArrow ? <div className="popover-arrow" /> : null}
<header className="popover-header">
<div className="popover-header__title">
@ -42,6 +49,7 @@ const Popover = ({
<button
className="fas fa-times popover-header__button"
title={t('close')}
data-testid="popover-close"
onClick={onClose}
/>
</div>
@ -76,6 +84,10 @@ Popover.propTypes = {
contentClassName: PropTypes.string,
className: PropTypes.string,
showArrow: PropTypes.bool,
mediumHeight: PropTypes.bool,
popoverRef: PropTypes.shape({
current: PropTypes.instanceOf(window.Element),
}),
};
export default class PopoverPortal extends PureComponent {

View File

@ -50,6 +50,7 @@ export default function reduceApp(state = {}, action) {
requestAccountTabs: {},
openMetaMaskTabs: {},
currentWindowTab: {},
showWhatsNewPopup: true,
...state,
};
@ -352,6 +353,12 @@ export default function reduceApp(state = {}, action) {
currentWindowTab: action.value,
};
case actionConstants.HIDE_WHATS_NEW_POPUP:
return {
...appState,
showWhatsNewPopup: false,
};
default:
return appState;
}
@ -364,3 +371,9 @@ export function setThreeBoxLastUpdated(lastUpdated) {
value: lastUpdated,
};
}
export function hideWhatsNewPopup() {
return {
type: actionConstants.HIDE_WHATS_NEW_POPUP,
};
}

View File

@ -13,7 +13,7 @@ import ConnectedSites from '../connected-sites';
import ConnectedAccounts from '../connected-accounts';
import { Tabs, Tab } from '../../components/ui/tabs';
import { EthOverview } from '../../components/app/wallet-overview';
import SwapsIntroPopup from '../swaps/intro-popup';
import WhatsNewPopup from '../../components/app/whats-new-popup';
import {
ASSET_ROUTE,
@ -64,19 +64,18 @@ export default class Home extends PureComponent {
connectedStatusPopoverHasBeenShown: PropTypes.bool,
defaultHomeActiveTabName: PropTypes.string,
onTabClick: PropTypes.func.isRequired,
setSwapsWelcomeMessageHasBeenShown: PropTypes.func.isRequired,
swapsWelcomeMessageHasBeenShown: PropTypes.bool.isRequired,
haveSwapsQuotes: PropTypes.bool.isRequired,
showAwaitingSwapScreen: PropTypes.bool.isRequired,
swapsFetchParams: PropTypes.object,
swapsEnabled: PropTypes.bool,
isMainnet: PropTypes.bool,
shouldShowWeb3ShimUsageNotification: PropTypes.bool.isRequired,
setWeb3ShimUsageAlertDismissed: PropTypes.func.isRequired,
originOfCurrentTab: PropTypes.string,
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired,
infuraBlocked: PropTypes.bool.isRequired,
showWhatsNewPopup: PropTypes.bool.isRequired,
hideWhatsNewPopup: PropTypes.func.isRequired,
notificationsToShow: PropTypes.bool.isRequired,
};
state = {
@ -323,10 +322,9 @@ export default class Home extends PureComponent {
history,
connectedStatusPopoverHasBeenShown,
isPopup,
swapsWelcomeMessageHasBeenShown,
setSwapsWelcomeMessageHasBeenShown,
swapsEnabled,
isMainnet,
notificationsToShow,
showWhatsNewPopup,
hideWhatsNewPopup,
} = this.props;
if (forgottenPassword) {
@ -344,8 +342,8 @@ export default class Home extends PureComponent {
exact
/>
<div className="home__container">
{!swapsWelcomeMessageHasBeenShown && swapsEnabled && isMainnet ? (
<SwapsIntroPopup onClose={setSwapsWelcomeMessageHasBeenShown} />
{notificationsToShow && showWhatsNewPopup ? (
<WhatsNewPopup onClose={hideWhatsNewPopup} />
) : null}
{isPopup && !connectedStatusPopoverHasBeenShown
? this.renderPopover()

View File

@ -12,6 +12,8 @@ import {
getWeb3ShimUsageStateForOrigin,
unconfirmedTransactionsCountSelector,
getInfuraBlocked,
getShowWhatsNewPopup,
getSortedNotificationsToShow,
} from '../../selectors';
import {
@ -21,16 +23,12 @@ import {
setShowRestorePromptToFalse,
setConnectedStatusPopoverHasBeenShown,
setDefaultHomeActiveTabName,
setSwapsWelcomeMessageHasBeenShown,
setWeb3ShimUsageAlertDismissed,
setAlertEnabledness,
} from '../../store/actions';
import { setThreeBoxLastUpdated } from '../../ducks/app/app';
import { setThreeBoxLastUpdated, hideWhatsNewPopup } from '../../ducks/app/app';
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask';
import {
getSwapsWelcomeMessageSeenStatus,
getSwapsFeatureLiveness,
} from '../../ducks/swaps/swaps';
import { getSwapsFeatureLiveness } from '../../ducks/swaps/swaps';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import {
ENVIRONMENT_TYPE_NOTIFICATION,
@ -97,7 +95,6 @@ const mapStateToProps = (state) => {
totalUnapprovedCount,
connectedStatusPopoverHasBeenShown,
defaultHomeActiveTabName,
swapsWelcomeMessageHasBeenShown: getSwapsWelcomeMessageSeenStatus(state),
haveSwapsQuotes: Boolean(Object.values(swapsState.quotes || {}).length),
swapsFetchParams: swapsState.fetchParams,
showAwaitingSwapScreen: swapsState.routeState === 'awaiting',
@ -106,6 +103,8 @@ const mapStateToProps = (state) => {
shouldShowWeb3ShimUsageNotification,
pendingConfirmations,
infuraBlocked: getInfuraBlocked(state),
notificationsToShow: getSortedNotificationsToShow(state).length > 0,
showWhatsNewPopup: getShowWhatsNewPopup(state),
};
};
@ -126,12 +125,11 @@ const mapDispatchToProps = (dispatch) => ({
setConnectedStatusPopoverHasBeenShown: () =>
dispatch(setConnectedStatusPopoverHasBeenShown()),
onTabClick: (name) => dispatch(setDefaultHomeActiveTabName(name)),
setSwapsWelcomeMessageHasBeenShown: () =>
dispatch(setSwapsWelcomeMessageHasBeenShown()),
setWeb3ShimUsageAlertDismissed: (origin) =>
setWeb3ShimUsageAlertDismissed(origin),
disableWeb3ShimUsageAlert: () =>
setAlertEnabledness(ALERT_TYPES.web3ShimUsage, false),
hideWhatsNewPopup: () => dispatch(hideWhatsNewPopup()),
});
export default compose(

View File

@ -102,13 +102,13 @@ export default class Routes extends Component {
currentCurrency,
pageChanged,
setCurrentCurrencyToUSD,
history,
} = this.props;
if (!currentCurrency) {
setCurrentCurrencyToUSD();
}
this.props.history.listen((locationObj, action) => {
history.listen((locationObj, action) => {
if (action === 'PUSH') {
pageChanged(locationObj.pathname);
}

View File

@ -6,7 +6,6 @@
@import 'dropdown-search-list/index';
@import 'exchange-rate-display/index';
@import 'fee-card/index';
@import 'intro-popup/index';
@import 'loading-swaps-quotes/index';
@import 'main-quote-summary/index';
@import 'searchable-item-list/index';

View File

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IntroPopup renders the component with initial props 1`] = `
<div>
<div
class="intro-popup"
/>
</div>
`;

View File

@ -1 +0,0 @@
export { default } from './intro-popup';

View File

@ -1,71 +0,0 @@
.intro-popup {
&__liquidity-sources-label {
@include H7;
font-weight: bold;
margin-bottom: 6px;
color: $Black-100;
@media screen and (min-width: 576px) {
@include H6;
}
}
&__learn-more-header {
@include H4;
font-weight: bold;
margin-bottom: 12px;
margin-top: 16px;
}
&__learn-more-link {
@include H6;
color: $Blue-500;
margin-bottom: 8px;
cursor: pointer;
}
&__content {
margin-left: 24px;
> img {
width: 96%;
margin-left: -9px;
}
}
&__footer {
border-top: none;
}
&__button {
border-radius: 100px;
height: 44px;
}
&__source-logo-container {
width: 276px;
display: flex;
justify-content: center;
align-items: center;
padding: 20px 16px;
background: $Grey-000;
border-radius: 8px;
@media screen and (min-width: 576px) {
width: 412px;
img {
width: 364px;
}
}
}
&__popover {
@media screen and (min-width: 576px) {
width: 460px;
}
}
}

View File

@ -1,108 +0,0 @@
import React, { useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
import { I18nContext } from '../../../contexts/i18n';
import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes';
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
import { getSwapsDefaultToken } from '../../../selectors';
import Button from '../../../components/ui/button';
import Popover from '../../../components/ui/popover';
export default function IntroPopup({ onClose }) {
const dispatch = useDispatch(useDispatch);
const history = useHistory();
const t = useContext(I18nContext);
const swapsDefaultToken = useSelector(getSwapsDefaultToken);
const enteredSwapsEvent = useNewMetricEvent({
event: 'Swaps Opened',
properties: {
source: 'Intro popup',
active_currency: swapsDefaultToken.symbol,
},
category: 'swaps',
});
const blogPostVisitedEvent = useNewMetricEvent({
event: 'Blog Post Visited ',
category: 'swaps',
});
const contractAuditVisitedEvent = useNewMetricEvent({
event: 'Contract Audit Visited',
category: 'swaps',
});
const productOverviewDismissedEvent = useNewMetricEvent({
event: 'Product Overview Dismissed',
category: 'swaps',
});
return (
<div className="intro-popup">
<Popover
className="intro-popup__popover"
title={t('swapIntroPopupTitle')}
subtitle={t('swapIntroPopupSubTitle')}
onClose={() => {
productOverviewDismissedEvent();
onClose();
}}
footerClassName="intro-popup__footer"
footer={
<Button
type="confirm"
className="intro-popup__button"
onClick={() => {
onClose();
enteredSwapsEvent();
dispatch(setSwapsFromToken(swapsDefaultToken));
history.push(BUILD_QUOTE_ROUTE);
}}
>
{t('swapStartSwapping')}
</Button>
}
>
<div className="intro-popup__content">
<div className="intro-popup__liquidity-sources-label">
{t('swapIntroLiquiditySourcesLabel')}
</div>
<div className="intro-popup__source-logo-container">
<img src="images/source-logos-all.svg" alt="" />
</div>
<div className="intro-popup__learn-more-header">
{t('swapIntroLearnMoreHeader')}
</div>
<div
className="intro-popup__learn-more-link"
onClick={() => {
global.platform.openTab({
url:
'https://medium.com/metamask/introducing-metamask-swaps-84318c643785',
});
blogPostVisitedEvent();
}}
>
{t('swapIntroLearnMoreLink')}
</div>
<div
className="intro-popup__learn-more-link"
onClick={() => {
global.platform.openTab({
url:
'https://diligence.consensys.net/audits/private/lsjipyllnw2/',
});
contractAuditVisitedEvent();
}}
>
{t('swapLearnMoreContractsAuditReview')}
</div>
</div>
</Popover>
</div>
);
}
IntroPopup.propTypes = {
onClose: PropTypes.func.isRequired,
};

View File

@ -1,24 +0,0 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
} from '../../../../../test/jest';
import IntroPopup from '.';
const createProps = (customProps = {}) => {
return {
onClose: jest.fn(),
...customProps,
};
};
describe('IntroPopup', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { container } = renderWithProvider(<IntroPopup {...props} />, store);
expect(container).toMatchSnapshot();
});
});

View File

@ -494,3 +494,36 @@ export function getNativeCurrencyImage(state) {
export function getNextSuggestedNonce(state) {
return Number(state.metamask.nextNonce);
}
export function getShowWhatsNewPopup(state) {
return state.appState.showWhatsNewPopup;
}
/**
* @typedef {Object} Notification
* @property {number} id - A unique identifier for the notification
* @property {string} date - A date in YYYY-MM-DD format, identifying when the notification was first committed
*/
/**
* Notifications are managed by the notification controller and referenced by
* `state.metamask.notifications`. This function returns a list of notifications
* the can be shown to the user. This list includes all notifications that do not
* have a truthy `isShown` property.
*
* The returned notifications are sorted by date.
*
* @param {Object} state - the redux state object
* @returns {Notification[]} An array of notifications that can be shown to the user
*/
export function getSortedNotificationsToShow(state) {
const notifications = Object.values(state.metamask.notifications);
const notificationsToShow = notifications.filter(
(notification) => !notification.isShown,
);
const notificationsSortedByDate = notificationsToShow.sort(
(a, b) => new Date(b.date) - new Date(a.date),
);
return notificationsSortedByDate;
}

View File

@ -112,3 +112,6 @@ export const LOADING_TOKEN_PARAMS_FINISHED = 'LOADING_TOKEN_PARAMS_FINISHED';
export const SET_REQUEST_ACCOUNT_TABS = 'SET_REQUEST_ACCOUNT_TABS';
export const SET_CURRENT_WINDOW_TAB = 'SET_CURRENT_WINDOW_TAB';
export const SET_OPEN_METAMASK_TAB_IDS = 'SET_OPEN_METAMASK_TAB_IDS';
// Home Screen
export const HIDE_WHATS_NEW_POPUP = 'HIDE_WHATS_NEW_POPUP';

View File

@ -2573,16 +2573,6 @@ export function setConnectedStatusPopoverHasBeenShown() {
};
}
export function setSwapsWelcomeMessageHasBeenShown() {
return () => {
background.setSwapsWelcomeMessageHasBeenShown((err) => {
if (err) {
throw new Error(err.message);
}
});
};
}
export async function setAlertEnabledness(alertId, enabledness) {
await promisifiedBackground.setAlertEnabledness(alertId, enabledness);
}
@ -2888,3 +2878,9 @@ export function trackMetaMetricsEvent(payload, options) {
export function trackMetaMetricsPage(payload, options) {
return promisifiedBackground.trackMetaMetricsPage(payload, options);
}
export function updateViewedNotifications(notificationIdViewedStatusMap) {
return promisifiedBackground.updateViewedNotifications(
notificationIdViewedStatusMap,
);
}