From 9e84704a2400aaa391d086b56f7d4feb0a2f32ed Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Wed, 16 Oct 2024 16:08:42 -0400 Subject: [PATCH 1/7] show notification properly --- client/globals.js | 96 --------------- components/Layout.js | 4 + hooks/usePrompt.js | 89 ++++++++++++++ package-lock.json | 273 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + styles/global.scss | 63 ++++++++++ 6 files changed, 430 insertions(+), 96 deletions(-) create mode 100644 hooks/usePrompt.js diff --git a/client/globals.js b/client/globals.js index d55537156..73b72dfe5 100644 --- a/client/globals.js +++ b/client/globals.js @@ -114,102 +114,6 @@ window.translateErrorMessage = function (error) { } } -// Flash Alert Functions (main.js) -var flashMessageTimer -window.generalPrompt = function (type, content, options) { - var defaults = { - html: false, - noTimeout: false, - scrollToTop: true, - overlay: true, - } - options = _.assign(defaults, options) - - var $outer = $('#flash-message-container') - $outer.hide() - if (type === 'error') { - $outer.removeClass('alert-success alert-warning').addClass('alert-danger') - } else if (type === 'message') { - $outer.removeClass('alert-danger alert-warning').addClass('alert-success') - } else if (type === 'info') { - $outer.removeClass('alert-danger alert-success').addClass('alert-warning') - } - - if (options.overlay) { - $outer.addClass('fixed-overlay') - } else { - $outer.removeClass('fixed-overlay') - } - - clearTimeout(flashMessageTimer) - - var msgHtml - if (options.html) { - msgHtml = content - } else if (type === 'error') { - var errorLabel = options.hideErrorLabel ? null : 'Error: ' - msgHtml = _.flatten([errorLabel, translateErrorMessage(content)]) - } else { - msgHtml = view.iMess(content) - } - $outer.find('.alert-content').empty() - $outer.find('.alert-content').append(msgHtml) - $outer.slideDown() - - if (options.scrollToTop && window.scrollY > 0) { - $('html, body').animate({ scrollTop: 0 }, 400) - } - - if (!options.noTimeout) { - flashMessageTimer = setTimeout(function () { - $outer.slideUp() - }, options.timeout ?? 8000) - } -} - -window.promptError = function (error, options) { - generalPrompt('error', error, options) -} - -window.promptMessage = function (message, options) { - generalPrompt('message', message, options) -} - -window.promptLogin = function (user) { - var redirectParam = window.legacyScripts - ? '' - : '?redirect=' + - encodeURIComponent( - window.location.pathname + window.location.search + window.location.hash - ) - - if (_.isEmpty(user) || _.startsWith(user.id, 'guest_')) { - generalPrompt( - 'message', - 'Please Login to proceed', - { html: true } - ) - } else { - generalPrompt( - 'message', - 'Please Login as a different user to proceed', - { html: true } - ) - } -} - -window.clearMessage = function () { - $('#flash-message-container').slideUp() -} - -// Global Event Handlers (index.js) -// Flash message bar -$('#flash-message-container button.close').on('click', function () { - $('#flash-message-container').slideUp() -}) - // Dropdowns $(document).on('click', function (event) { if (!$(event.target).closest('.dropdown').length) { diff --git a/components/Layout.js b/components/Layout.js index 7c1324b18..befeadc72 100644 --- a/components/Layout.js +++ b/components/Layout.js @@ -7,6 +7,7 @@ import Footer from './Footer' import FooterMinimal from './FooterMinimal' import FeedbackModal from './FeedbackModal' import BibtexModal from './BibtexModal' +import usePrompt from '../hooks/usePrompt' export default function Layout({ children, @@ -17,6 +18,9 @@ export default function Layout({ fullWidth, minimalFooter, }) { + const promptFunctions = usePrompt() + Object.assign(global, promptFunctions) + return ( <> diff --git a/hooks/usePrompt.js b/hooks/usePrompt.js new file mode 100644 index 000000000..2d1eafdca --- /dev/null +++ b/hooks/usePrompt.js @@ -0,0 +1,89 @@ +import Notification from 'rc-notification' +import { useEffect, useState } from 'react' +import Icon from '../components/Icon' +import Markdown from '../components/EditorComponents/Markdown' +import useBreakpoint from './useBreakPoint' + +export default function usePrompt() { + const [notificationInstance, setNotificationInstance] = useState(null) + const isMobile = !useBreakpoint('md') + const canClose = !isMobile + + useEffect(() => { + Notification.newInstance( + { + maxCount: 2, + ...(canClose && { closeIcon: }), + }, + (notification) => { + setNotificationInstance(notification) + } + ) + }, []) + return { + promptOk: (message) => + notificationInstance?.notice({ + content: ( + <> + + + + ), + duration: 2, + }), + promptMessage: (message) => + notificationInstance?.notice({ + content: ( + <> + + + + ), + duration: 2, + closable: canClose, + }), + promptError: (message) => + notificationInstance?.notice({ + content: ( + <> + + + + ), + duration: 4000, + closable: canClose, + }), + clearMessage: () => { + notificationInstance?.destroy() + // new instance after destroy otherwise calling destroy again will fail + Notification.newInstance( + { + maxCount: 2, + ...(canClose && { closeIcon: }), + }, + (notification) => { + setNotificationInstance(notification) + } + ) + }, + promptLogin: () => + notificationInstance?.notice({ + content: ( + <> + + Please  + + Login + +  to proceed + + ), + duration: 4, + closable: canClose, + }), + } +} diff --git a/package-lock.json b/package-lock.json index 701aadc0d..35264391c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "next": "^13.5.6", "next-build-id": "^3.0.0", "query-string": "^7.1.3", + "rc-notification": "^3.3.1", "rc-picker": "^4.6.14", "rc-steps": "^6.0.1", "rc-virtual-list": "^3.11.3", @@ -4190,6 +4191,14 @@ "node": ">=0.4.0" } }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "dependencies": { + "object-assign": "4.x" + } + }, "node_modules/address": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", @@ -4813,6 +4822,20 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5213,6 +5236,19 @@ "node": ">= 10" } }, + "node_modules/component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "dependencies": { + "component-indexof": "0.0.3" + } + }, + "node_modules/component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5246,6 +5282,13 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, "node_modules/core-js-compat": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", @@ -5329,6 +5372,15 @@ "node": ">=0.5.2" } }, + "node_modules/css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "dependencies": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -10573,6 +10625,11 @@ "node": "*" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -11017,6 +11074,45 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "dependencies": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "node_modules/rc-animate/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-animate/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rc-motion": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.3.tgz", @@ -11031,6 +11127,35 @@ "react-dom": ">=16.9.0" } }, + "node_modules/rc-notification": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-3.3.1.tgz", + "integrity": "sha512-U5+f4BmBVfMSf3OHSLyRagsJ74yKwlrQAtbbL5ijoA0F2C60BufwnOcHG18tVprd7iaIjzZt1TKMmQSYSvgrig==", + "dependencies": { + "babel-runtime": "6.x", + "classnames": "2.x", + "prop-types": "^15.5.8", + "rc-animate": "2.x", + "rc-util": "^4.0.4" + } + }, + "node_modules/rc-notification/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-notification/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rc-overflow": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.2.tgz", @@ -11181,6 +11306,11 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-select": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.1.tgz", @@ -11707,6 +11837,11 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16887,6 +17022,14 @@ "acorn": "^8.11.0" } }, + "add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "requires": { + "object-assign": "4.x" + } + }, "address": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", @@ -17358,6 +17501,22 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -17629,6 +17788,19 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, + "component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "requires": { + "component-indexof": "0.0.3" + } + }, + "component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -17659,6 +17831,11 @@ "toggle-selection": "^1.0.6" } }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, "core-js-compat": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", @@ -17722,6 +17899,15 @@ "integrity": "sha512-65Mtei8+EkSIK+5Ie4gpWXoJ/5bgpqPXFknHHXAyhDqKsEAAzUslGd8mOeawbfcuQ8fADNKcF4xQA3fqlZJ8Ig==", "dev": true }, + "css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "requires": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -21571,6 +21757,11 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -21870,6 +22061,47 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "requires": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "requires": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "rc-motion": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.3.tgz", @@ -21880,6 +22112,37 @@ "rc-util": "^5.43.0" } }, + "rc-notification": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-3.3.1.tgz", + "integrity": "sha512-U5+f4BmBVfMSf3OHSLyRagsJ74yKwlrQAtbbL5ijoA0F2C60BufwnOcHG18tVprd7iaIjzZt1TKMmQSYSvgrig==", + "requires": { + "babel-runtime": "6.x", + "classnames": "2.x", + "prop-types": "^15.5.8", + "rc-animate": "2.x", + "rc-util": "^4.0.4" + }, + "dependencies": { + "rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "requires": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "rc-overflow": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.2.tgz", @@ -21975,6 +22238,11 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-select": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.1.tgz", @@ -22366,6 +22634,11 @@ "has-property-descriptors": "^1.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index df16d0630..c8fb51386 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "next": "^13.5.6", "next-build-id": "^3.0.0", "query-string": "^7.1.3", + "rc-notification": "^3.3.1", "rc-picker": "^4.6.14", "rc-steps": "^6.0.1", "rc-virtual-list": "^3.11.3", diff --git a/styles/global.scss b/styles/global.scss index a38425c1c..34bba42ad 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -941,3 +941,66 @@ header { } } } + +// Notification +.rc-notification { + position: fixed; + z-index: 10000; + width: 100%; + top: 0 !important; + left: 0 !important; + box-sizing: border-box; + pointer-events: none; + + .rc-notification-notice { + display: block; + margin: 1rem; + text-align: center; + right: 0 !important; + + .rc-notification-notice-content { + display: inline-flex; + align-items: baseline; + padding: 8px; + background-color: white; + border-radius: 8px; + box-shadow: rgba(0, 0, 0, 0.08) 0px 6px 16px 0px; + pointer-events: all; + text-align: left; + margin-right: -1.75rem; + padding-right: 2.5rem; + + @media #{constants.$phone-only} { + margin: 0; + padding-right: 0.5rem; + } + font-size: 1rem; + + .glyphicon { + margin-right: 0.5rem; + } + .glyphicon-ok-sign { + color: green; + } + .glyphicon-remove-sign { + color: red; + } + .glyphicon-info-sign { + color: yellowgreen; + } + + p { + margin-bottom: 0; + } + } + + .rc-notification-notice-close { + pointer-events: all; + margin-right: 0; + cursor: pointer; + &:focus { + outline: none; + } + } + } +} From 8cf01da867528faf64c294e1c256ba02fa9c0c3f Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Tue, 19 Nov 2024 15:14:46 -0500 Subject: [PATCH 2/7] update style --- hooks/usePrompt.js | 27 +++++++-------------------- styles/global.scss | 31 ++++++++++++++++++------------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/hooks/usePrompt.js b/hooks/usePrompt.js index 2d1eafdca..ab43d2377 100644 --- a/hooks/usePrompt.js +++ b/hooks/usePrompt.js @@ -21,36 +21,24 @@ export default function usePrompt() { ) }, []) return { - promptOk: (message) => - notificationInstance?.notice({ - content: ( - <> - - - - ), - duration: 2, - }), promptMessage: (message) => notificationInstance?.notice({ content: ( - <> - +
- +
), - duration: 2, + duration: 3, closable: canClose, }), promptError: (message) => notificationInstance?.notice({ content: ( - <> - - - +
+ +
), - duration: 4000, + duration: 4, closable: canClose, }), clearMessage: () => { @@ -70,7 +58,6 @@ export default function usePrompt() { notificationInstance?.notice({ content: ( <> - Please  Date: Wed, 20 Nov 2024 12:52:58 -0500 Subject: [PATCH 3/7] some changes based on feedback --- hooks/usePrompt.js | 53 +++++++++++++++++++++++++++++----------------- styles/global.scss | 21 +++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/hooks/usePrompt.js b/hooks/usePrompt.js index ab43d2377..7aac3e954 100644 --- a/hooks/usePrompt.js +++ b/hooks/usePrompt.js @@ -8,12 +8,20 @@ export default function usePrompt() { const [notificationInstance, setNotificationInstance] = useState(null) const isMobile = !useBreakpoint('md') const canClose = !isMobile + const messageDuration = isMobile ? 2 : 3 + const errorDuration = isMobile ? 2 : 4 useEffect(() => { Notification.newInstance( { maxCount: 2, - ...(canClose && { closeIcon: }), + ...(canClose && { + closeIcon: ( + + ), + }), }, (notification) => { setNotificationInstance(notification) @@ -28,7 +36,7 @@ export default function usePrompt() { ), - duration: 3, + duration: messageDuration, closable: canClose, }), promptError: (message) => @@ -38,26 +46,14 @@ export default function usePrompt() { ), - duration: 4, + duration: errorDuration, closable: canClose, }), - clearMessage: () => { - notificationInstance?.destroy() - // new instance after destroy otherwise calling destroy again will fail - Notification.newInstance( - { - maxCount: 2, - ...(canClose && { closeIcon: }), - }, - (notification) => { - setNotificationInstance(notification) - } - ) - }, + promptLogin: () => notificationInstance?.notice({ content: ( - <> + ), - duration: 4, + duration: errorDuration, closable: canClose, }), + clearMessage: () => { + notificationInstance?.destroy() + // new instance after destroy otherwise calling destroy again will fail + Notification.newInstance( + { + maxCount: 2, + ...(canClose && { + closeIcon: ( + + ), + }), + }, + (notification) => { + setNotificationInstance(notification) + } + ) + }, } } diff --git a/styles/global.scss b/styles/global.scss index ec782dc9d..a7859ba50 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -947,7 +947,7 @@ header { position: fixed; z-index: 10000; width: 100%; - top: 0 !important; + top: 50px !important; left: 0 !important; box-sizing: border-box; pointer-events: none; @@ -961,19 +961,21 @@ header { .rc-notification-notice-content { display: inline-flex; align-items: baseline; - padding: 8px; + padding-top: 8px; + padding-bottom: 8px; + padding-left: 1.5rem; + padding-right: 4.5rem; background-color: white; border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.08) 0px 6px 16px 0px; pointer-events: all; text-align: left; margin-right: -1.75rem; - padding-right: 2.5rem; line-height: 21px; @media #{constants.$phone-only} { margin: 0; - padding-right: 0.5rem; + padding-right: 1.5rem; } font-size: 0.875rem; @@ -992,20 +994,23 @@ header { border-color: #ebccd1; color: #a94442; } + + &:has(.login) { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; + } } .rc-notification-notice-close { pointer-events: all; + margin-left: -2.25rem; margin-right: 0; color: darkgray; cursor: pointer; &:focus { outline: none; } - - @media #{constants.$phone-only} { - margin-left: -1rem; - } } } } From 22d870e151deda16e409b954929a426f904237bc Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Thu, 21 Nov 2024 11:42:36 -0500 Subject: [PATCH 4/7] style changes, pause on hover --- hooks/usePrompt.js | 3 +++ styles/global.scss | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/hooks/usePrompt.js b/hooks/usePrompt.js index 7aac3e954..fcb2fba4a 100644 --- a/hooks/usePrompt.js +++ b/hooks/usePrompt.js @@ -38,6 +38,7 @@ export default function usePrompt() { ), duration: messageDuration, closable: canClose, + pauseOnHover: true, }), promptError: (message) => notificationInstance?.notice({ @@ -48,6 +49,7 @@ export default function usePrompt() { ), duration: errorDuration, closable: canClose, + pauseOnHover: true, }), promptLogin: () => @@ -67,6 +69,7 @@ export default function usePrompt() { ), duration: errorDuration, closable: canClose, + pauseOnHover: true, }), clearMessage: () => { notificationInstance?.destroy() diff --git a/styles/global.scss b/styles/global.scss index a7859ba50..6ac470c58 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -957,14 +957,25 @@ header { margin: 1rem; text-align: center; right: 0 !important; + animation-duration: 0.3s; + animation-name: slidedown; + + @keyframes slidedown { + 0% { + transform: translateY(-100%); + } + 100% { + transform: translateY(0); + } + } .rc-notification-notice-content { display: inline-flex; align-items: baseline; padding-top: 8px; padding-bottom: 8px; - padding-left: 1.5rem; - padding-right: 4.5rem; + padding-left: 5%; + padding-right: 10%; background-color: white; border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.08) 0px 6px 16px 0px; @@ -972,10 +983,11 @@ header { text-align: left; margin-right: -1.75rem; line-height: 21px; + width: 80%; @media #{constants.$phone-only} { margin: 0; - padding-right: 1.5rem; + padding-right: 5%; } font-size: 0.875rem; From 42a55d2779f3651f2260b2f542c09fd63b357cec Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Thu, 21 Nov 2024 14:17:32 -0500 Subject: [PATCH 5/7] update test --- components/BasicModal.js | 3 +- components/DblpImportModal.js | 3 +- components/FlashAlert.js | 22 -------------- components/Layout.js | 2 -- pages/profile/activate.js | 4 ++- pages/profile/edit.js | 2 +- styles/global.scss | 15 +--------- styles/pages/reset.scss | 4 --- tests/profilePage.ts | 26 +++++----------- tests/registerPage.ts | 56 ++++++++--------------------------- 10 files changed, 28 insertions(+), 109 deletions(-) delete mode 100644 components/FlashAlert.js diff --git a/components/BasicModal.js b/components/BasicModal.js index 9a07a39c4..0d13a58e9 100644 --- a/components/BasicModal.js +++ b/components/BasicModal.js @@ -1,4 +1,4 @@ -/* globals $: false */ +/* globals $,clearMessage: false */ import { useEffect, useRef } from 'react' import SpinnerButton from './SpinnerButton' @@ -25,6 +25,7 @@ export default function BasicModal({ } }) $(modalRef.current).on('show.bs.modal', (e) => { + clearMessage() if (typeof onOpen === 'function') { onOpen(e.relatedTarget) } diff --git a/components/DblpImportModal.js b/components/DblpImportModal.js index c3ff8d66d..14c45c796 100644 --- a/components/DblpImportModal.js +++ b/components/DblpImportModal.js @@ -1,4 +1,4 @@ -/* globals $: false */ +/* globals $,clearMessage: false */ import { useState, useRef, useEffect, useContext } from 'react' import { nanoid } from 'nanoid' @@ -231,6 +231,7 @@ export default function DblpImportModal({ profileId, profileNames, updateDBLPUrl useEffect(() => { $(modalEl.current).on('show.bs.modal', () => { + clearMessage() // read current dblp url from input let dblpInputVal = $('#dblp_url').val().trim() if (dblpInputVal.endsWith('.html')) dblpInputVal = dblpInputVal.slice(0, -5) diff --git a/components/FlashAlert.js b/components/FlashAlert.js deleted file mode 100644 index c5d6557b7..000000000 --- a/components/FlashAlert.js +++ /dev/null @@ -1,22 +0,0 @@ -const FlashAlert = () => ( - -) - -export default FlashAlert diff --git a/components/Layout.js b/components/Layout.js index befeadc72..5cc0106b3 100644 --- a/components/Layout.js +++ b/components/Layout.js @@ -2,7 +2,6 @@ import Head from 'next/head' import Nav from './Nav' import Banner from './Banner' import EditBanner from './EditBanner' -import FlashAlert from './FlashAlert' import Footer from './Footer' import FooterMinimal from './FooterMinimal' import FeedbackModal from './FeedbackModal' @@ -74,7 +73,6 @@ gtag('config', '${process.env.GA_PROPERTY_ID}', { {editBannerContent} -
diff --git a/pages/profile/activate.js b/pages/profile/activate.js index 2304ff35e..effc96075 100644 --- a/pages/profile/activate.js +++ b/pages/profile/activate.js @@ -72,8 +72,10 @@ const ActivateProfile = ({ appContext }) => { if (query.token) { loadActivatableProfile(query.token) } else { - promptError('Invalid profile activation link. Please check your email and try again.') router.replace('/') + setTimeout(() => { + promptError('Invalid profile activation link. Please check your email and try again.') + }, 0) } }, [query]) diff --git a/pages/profile/edit.js b/pages/profile/edit.js index ab7abf851..2be0053c4 100644 --- a/pages/profile/edit.js +++ b/pages/profile/edit.js @@ -134,7 +134,7 @@ export default function ProfileEdit({ appContext }) { }) loadProfile() } catch (apiError) { - promptError(marked(`**Error:** ${apiError.message}`), { html: true }) + promptError(apiError.message) setSaveProfileErrors( apiError.errors?.map((p) => p.details?.path) ?? [apiError?.details?.path] ) diff --git a/styles/global.scss b/styles/global.scss index 6ac470c58..29ecfc04b 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -575,7 +575,6 @@ header { } // Flash message -#flash-message-container, #edit-banner { padding: 12px 0; margin: 0; @@ -601,22 +600,10 @@ header { padding-right: 5px; } - &.alert-success { - a { - color: constants.$mediumBlue; - text-decoration: underline; - } - } &.alert-warning { background-color: constants.$sandyBrown; border-color: constants.$backgroundGray; } - &.fixed-overlay { - position: fixed; - top: 51px; // height of navbar - width: 100%; - z-index: 1030; - } } #edit-banner { .alert-content span { @@ -947,7 +934,7 @@ header { position: fixed; z-index: 10000; width: 100%; - top: 50px !important; + top: 80px !important; left: 0 !important; box-sizing: border-box; pointer-events: none; diff --git a/styles/pages/reset.scss b/styles/pages/reset.scss index 0415639ea..28686a539 100644 --- a/styles/pages/reset.scss +++ b/styles/pages/reset.scss @@ -17,10 +17,6 @@ main.reset { margin-top: 0.25rem; margin-bottom: 0.5rem; } - .alert-success { - margin-top: 2rem; - margin-bottom: 2rem; - } .spinner-container { margin-top: 3rem; } diff --git a/tests/profilePage.ts b/tests/profilePage.ts index 4b68739a7..ff0707c59 100644 --- a/tests/profilePage.ts +++ b/tests/profilePage.ts @@ -32,9 +32,9 @@ const userARole = Role(`http://localhost:${process.env.NEXT_PORT}`, async (t) => const userBAlternateId = '~Di_Xu1' // #region long repeated selectors -const errorMessageSelector = Selector('#flash-message-container', { +const errorMessageSelector = Selector('.rc-notification-notice-content', { visibilityCheck: true, -}) +}).nth(-1) const editFullNameInputSelector = Selector('input:not([readonly]).full-name') const nameSectionPlusIconSelector = Selector('section').find('.glyphicon-plus-sign') const emailSectionPlusIconSelector = Selector('section').find('.glyphicon-plus-sign') @@ -69,8 +69,7 @@ const firstHistoryEndInput = Selector('div.history') .find('input') .withAttribute('placeholder', 'end year') .nth(0) -const messageSelector = Selector('span').withAttribute('class', 'important_message') -const messagePanelSelector = Selector('#flash-message-container') +const messageSelector = Selector('.rc-notification-notice-content').nth(-1) const step0Names = Selector('div[step="0"]').find('div[role="button"]') const step1PeronalInfo = Selector('div[step="1"]').find('div[role="button"]') const step2Emails = Selector('div[step="2"]').find('div[role="button"]') @@ -776,7 +775,7 @@ test('profile should be auto merged', async (t) => { .click(Selector('button').withText('Confirm').filterVisible()) .expect(Selector('a').withText('Merge Profiles').exists) .notOk() - .expect(Selector('#flash-message-container').find('div.alert-content').innerText) + .expect(Selector('.rc-notification-notice-content').nth(-1).innerText) .contains(`A confirmation email has been sent to ${userF.email}`) // text box to enter code should be displayed @@ -817,7 +816,7 @@ test('profile should be auto merged', async (t) => { .expect(Selector('button').withText('Confirm Profile Merge').exists) .ok() .click(Selector('button').withText('Confirm Profile Merge')) - .expect(Selector('div.alert-content').innerText) + .expect(errorMessageSelector.innerText) .contains('Thank you for confirming the profile merge.') // email should have been added to hasTaskUser's profile @@ -887,7 +886,7 @@ test('#85 confirm profile email message', async (t) => { .click(Selector('button').withText('Confirm').filterVisible()) .typeText(editEmailInputSelector, 'x@x.com', { replace: true }) .click(Selector('button').withText('Confirm').filterVisible()) - .expect(Selector('#flash-message-container').find('div.alert-content').innerText) + .expect(Selector('.rc-notification-notice-content').nth(-1).innerText) .contains('A confirmation email has been sent to x@x.com') // text box to enter code should be displayed .expect(Selector('button').withText('Verify').nth(0).visible) @@ -1030,16 +1029,12 @@ test('check if a user can add multiple emails without entering verification toke 'aab@alternate.com' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to aab@alternate.com with confirmation instructions' ) .typeText(Selector('input[placeholder="Enter Verification Token"]'), '000000') .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('aab@alternate.com has been verified') @@ -1051,17 +1046,13 @@ test('check if a user can add multiple emails without entering verification toke 'aac@alternate.com' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to aac@alternate.com with confirmation instructions' ) .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) - .eql('token must NOT have fewer than 1 characters') + .eql('Error: token must NOT have fewer than 1 characters') .click(saveProfileButton) .click(cancelButton) @@ -1075,9 +1066,6 @@ test('check if a user can add multiple emails without entering verification toke .withText('Confirmed').exists ) .ok() - - .expect(Selector('span').withText('aac@alternate.com').exists) - .ok() .expect(Selector('span').withText('aac@alternate.com').parent().textContent) .notContains('Confirmed') }) diff --git a/tests/registerPage.ts b/tests/registerPage.ts index 10ceacbdf..e1417c398 100644 --- a/tests/registerPage.ts +++ b/tests/registerPage.ts @@ -38,8 +38,7 @@ const confirmPasswordInputSelector = Selector('input').withAttribute( ) const sendActivationLinkButtonSelector = Selector('button').withText('Send Activation Link') const claimProfileButtonSelector = Selector('button').withText('Claim Profile') -const messageSelector = Selector('span').withAttribute('class', 'important_message') -const messagePanelSelector = Selector('#flash-message-container') +const messageSelector = Selector('.rc-notification-notice-content').nth(-1) const nextSectiomButtonSelector = Selector('button').withText('Next Section') fixture`Signup`.page`http://localhost:${process.env.NEXT_PORT}/signup`.before(async (ctx) => { @@ -222,11 +221,9 @@ test('create a new profile with an institutional email', async (t) => { test('enter invalid name', async (t) => { await t .typeText(fullNameInputSelector, 'abc 1') - .expect(Selector('.important_message').exists) - .ok() - .expect(Selector('.important_message').textContent) + .expect(messageSelector.innerText) .eql( - 'The name 1 is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' + 'Error: The name 1 is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' ) }) @@ -272,8 +269,6 @@ test('request a new activation link', async (t) => { await t .typeText(Selector('input').withAttribute('placeholder', 'Email'), 'melisa@test.com') .click(Selector('a').withText("Didn't receive email confirmation?")) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email with the subject "OpenReview signup confirmation" has been sent to melisa@test.com. Please click the link in this email to confirm your email address and complete registration.' @@ -387,22 +382,16 @@ test('update profile', async (t) => { 'melisa@umass.edu' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to melisa@umass.edu with confirmation instructions' ) .wait(500) .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) - .eql('token must NOT have fewer than 1 characters') + .eql('Error: token must NOT have fewer than 1 characters') .typeText(Selector('input[placeholder="Enter Verification Token"]'), '000000') .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('melisa@umass.edu has been verified') // check if buttons disappeared @@ -437,8 +426,6 @@ test('update profile', async (t) => { .click(nextSectiomButtonSelector) // last section expertise .expect(Selector('p').withText("last updated September 24, 2024").exists).ok() .click(Selector('button').withText('Register for OpenReview')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('Your OpenReview profile has been successfully created') .navigateTo(`http://localhost:${process.env.NEXT_PORT}/profile?id=~Melisa_Bok1`) @@ -469,8 +456,6 @@ test('do not allow merging from not registered profiles', async (t) => { 'melisa@umass.edu' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to melisa@umass.edu with confirmation instructions' @@ -481,11 +466,9 @@ test('do not allow merging from not registered profiles', async (t) => { .typeText(Selector('#email-input'), 'peter@test.com') .typeText(Selector('#password-input'), strongPassword) .click(Selector('button').withText('Login to OpenReview')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( - 'User not confirmed. Please click on "Didn\'t receive email confirmation?" to complete the registration.' + 'Error: User not confirmed. Please click on "Didn\'t receive email confirmation?" to complete the registration.' ) .selectText(Selector('#email-input')) .pressKey('delete') @@ -523,22 +506,16 @@ test('register a profile with an institutional email', async (t) => { 'kevin@test.com' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to kevin@test.com with confirmation instructions' ) .wait(500) .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) - .eql('token must NOT have fewer than 1 characters') + .eql('Error: token must NOT have fewer than 1 characters') .typeText(Selector('input[placeholder="Enter Verification Token"]'), '000000') .click(Selector('button').withText('Verify').nth(0)) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('kevin@test.com has been verified') // check if buttons disappeared @@ -566,8 +543,6 @@ test('register a profile with an institutional email', async (t) => { .click(nextSectiomButtonSelector) .click(nextSectiomButtonSelector) .click(Selector('button').withText('Register for OpenReview')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('Your OpenReview profile has been successfully created') }) @@ -578,28 +553,25 @@ fixture`Activate with errors` test('try to activate a profile with no token and get an error', async (t) => { await t .navigateTo(`http://localhost:${process.env.NEXT_PORT}/profile/activate`) - .expect(messagePanelSelector.exists) - .ok() + .wait(500) .expect(messageSelector.innerText) - .eql('Invalid profile activation link. Please check your email and try again.') + .eql('Error: Invalid profile activation link. Please check your email and try again.') }) test('try to activate a profile with empty token and get an error', async (t) => { await t .navigateTo(`http://localhost:${process.env.NEXT_PORT}/profile/activate?token=`) - .expect(messagePanelSelector.exists) - .ok() + .wait(500) .expect(messageSelector.innerText) - .eql('Invalid profile activation link. Please check your email and try again.') + .eql('Error: Invalid profile activation link. Please check your email and try again.') }) test('try to activate a profile with invalid token and get an error', async (t) => { await t .navigateTo(`http://localhost:${process.env.NEXT_PORT}/profile/activate?token=fhtbsk`) - .expect(messagePanelSelector.exists) - .ok() + .wait(500) .expect(messageSelector.innerText) - .eql('Activation token is not valid') + .eql('Error: Activation token is not valid') }) fixture`Reset password`.page`http://localhost:${process.env.NEXT_PORT}/reset`.before( @@ -660,8 +632,6 @@ test('add alternate email', async (t) => { 'melisa@alternate.com' ) .click(Selector('div.container.emails').find('button.confirm-button')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql( 'A confirmation email has been sent to melisa@alternate.com with confirmation instructions' @@ -717,8 +687,6 @@ test('update profile', async (t) => { ) .ok() .click(Selector('button').withText('Confirm Email')) - .expect(messagePanelSelector.exists) - .ok() .expect(messageSelector.innerText) .eql('Thank you for confirming your email melisa@alternate.com') }) From 24ed36936af09871aa8fa87c4f7e0dcf9a7af004 Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Thu, 21 Nov 2024 16:14:18 -0500 Subject: [PATCH 6/7] update test --- tests/registerPage.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/registerPage.ts b/tests/registerPage.ts index e1417c398..82c1af541 100644 --- a/tests/registerPage.ts +++ b/tests/registerPage.ts @@ -225,6 +225,15 @@ test('enter invalid name', async (t) => { .eql( 'Error: The name 1 is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' ) + .typeText(fullNameInputSelector, 'abc') + .expect(messageSelector.exists).notOk() // page calls clearMessage + .typeText(fullNameInputSelector, 'abc `') + .expect(messageSelector.innerText) + .eql( + 'Error: The name ` is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' + ) + .click(Selector('.rc-notification-notice-close')) // close message + .expect(messageSelector.exists).notOk() }) test('enter valid name invalid email and change to valid email and register', async (t) => { From c464c442fb435dd8d53c87ba1b6fcfe51d7dbc67 Mon Sep 17 00:00:00 2001 From: xkopenreview Date: Fri, 22 Nov 2024 12:12:57 -0500 Subject: [PATCH 7/7] update code calling prompt --- client/webfield-v2.js | 12 ++++----- .../EditorComponents/FileUploadWidget.js | 4 +-- components/NoteEditor.js | 6 ++--- components/NoteEditorForm.js | 3 ++- components/browser/ProfileEntity.js | 4 +-- components/forum/ChatEditorForm.js | 15 ++++++----- components/forum/ChatReply.js | 6 ++--- components/forum/FilterForm.js | 2 +- components/forum/ForumReply.js | 2 +- components/group/GroupContent.js | 2 +- components/group/GroupContentScripts.js | 17 +++--------- components/group/GroupGeneral.js | 2 +- components/group/GroupMembers.js | 11 +++----- components/group/GroupUICode.js | 2 +- components/invitation/DateProcessesEditor.js | 6 ++--- components/invitation/InvitationCode.js | 8 +++--- components/invitation/InvitationGeneral.js | 8 +++--- components/invitation/InvitationReply.js | 14 +++++----- components/profile/NameSection.js | 2 +- components/profile/PersonalLinksSection.js | 8 +++--- components/webfield/NoteMetaReviewStatus.js | 6 ++--- components/webfield/NoteReviewStatus.js | 10 +++---- .../ProgramChairConsole/AreaChairStatus.js | 6 ++--- .../ProgramChairConsole/ReviewerStatus.js | 6 ++--- .../SeniorAreaChairStatus.js | 12 ++++----- .../SeniorAreaChairConsole/AreaChairStatus.js | 6 ++--- hooks/usePrompt.js | 26 +++++++------------ pages/login.js | 2 +- pages/profile/activate.js | 4 +-- pages/profile/edit.js | 4 +-- pages/signup.js | 4 +-- pages/user/moderation.js | 12 ++++----- styles/global.scss | 4 ++- tests/registerPage.ts | 4 +-- unitTests/FileUploadWidget.test.js | 5 +--- 35 files changed, 105 insertions(+), 140 deletions(-) diff --git a/client/webfield-v2.js b/client/webfield-v2.js index cccbc5827..a0db09c48 100644 --- a/client/webfield-v2.js +++ b/client/webfield-v2.js @@ -668,9 +668,7 @@ module.exports = (function () { } $('#message-reviewers-modal').modal('hide') - promptMessage('A reminder email has been sent to ' + view.prettyId(userId), { - overlay: true, - }) + promptMessage('A reminder email has been sent to ' + view.prettyId(userId)) postReviewerEmails(postData) $link.after(' (Last sent: ' + new Date().toLocaleDateString() + ')') @@ -700,7 +698,7 @@ module.exports = (function () { var name = $link.data('userName') if (!options.preferredEmailsInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } @@ -709,14 +707,14 @@ module.exports = (function () { var email = result.edges?.[0]?.tail if (!email) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } copy(`${name} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) }, function () { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') } ) return false diff --git a/components/EditorComponents/FileUploadWidget.js b/components/EditorComponents/FileUploadWidget.js index 2d723a810..e595b65c1 100644 --- a/components/EditorComponents/FileUploadWidget.js +++ b/components/EditorComponents/FileUploadWidget.js @@ -52,7 +52,7 @@ const FileUploadWidget = () => { ) } } catch (apiError) { - promptError(apiError.message, { scrollToTop: false }) + promptError(apiError.message) fileInputRef.current.value = '' } } @@ -86,7 +86,7 @@ const FileUploadWidget = () => { await sendChunksPromises setFileName(file.name) } catch (apiError) { - promptError(apiError.message, { scrollToTop: false }) + promptError(apiError.message) fileInputRef.current.value = '' } diff --git a/components/NoteEditor.js b/components/NoteEditor.js index 3d900b67a..c5ddb0d04 100644 --- a/components/NoteEditor.js +++ b/components/NoteEditor.js @@ -174,9 +174,7 @@ const NoteEditor = ({ const useCheckboxWidget = true const displayError = - typeof setErrorAlertMessage === 'function' - ? setErrorAlertMessage - : (p) => promptError(p, { scrollToTop: false }) + typeof setErrorAlertMessage === 'function' ? setErrorAlertMessage : (p) => promptError(p) const saveDraft = useCallback( throttle((fieldName, value) => { @@ -457,7 +455,7 @@ const NoteEditor = ({ const invitationEditReaderValues = invitation.edit.readers?.param?.enum ?? invitation.edit.readers?.param?.items?.map((p) => - p.value ?? p.prefix?.endsWith('*') ? p.prefix : `${p.prefix}.*` + (p.value ?? p.prefix?.endsWith('*')) ? p.prefix : `${p.prefix}.*` ) return addMissingReaders( diff --git a/components/NoteEditorForm.js b/components/NoteEditorForm.js index 075ea2c63..46274d30a 100644 --- a/components/NoteEditorForm.js +++ b/components/NoteEditorForm.js @@ -54,12 +54,13 @@ export default function NoteEditorForm({ const handleError = (errors) => { setLoading(false) + console.log('errors is ', errors) const err = errors?.[0] if (err === 'You do not have permission to create a note' || !user) { promptLogin(user) } else if (err) { - promptError(err, { scrollToTop: false }) + promptError(err) } else { promptError('An unknown error occurred. Please refresh the page and try again.') } diff --git a/components/browser/ProfileEntity.js b/components/browser/ProfileEntity.js index 4ea51e4ba..bece0dcce 100644 --- a/components/browser/ProfileEntity.js +++ b/components/browser/ProfileEntity.js @@ -161,9 +161,9 @@ export default function ProfileEntity(props) { const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${content.name.fullname} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } diff --git a/components/forum/ChatEditorForm.js b/components/forum/ChatEditorForm.js index 541be46b8..711762484 100644 --- a/components/forum/ChatEditorForm.js +++ b/components/forum/ChatEditorForm.js @@ -73,7 +73,7 @@ export default function ChatEditorForm({ setSignatureOptions(sigOptions) setSignature(sigOptions[0]?.value) } catch (err) { - promptError(err.message, { scrollToTop: false }) + promptError(err.message) } } @@ -134,7 +134,7 @@ export default function ChatEditorForm({ }) }) .catch((err) => { - promptError(err.message, { scrollToTop: false }) + promptError(err.message) setLoading(false) }) } @@ -175,9 +175,11 @@ export default function ChatEditorForm({ > {replyToNote && (
-
{ - scrollToNote(replyToNote.id) - }}> +
{ + scrollToNote(replyToNote.id) + }} + > {/* {' '} */} Replying to {prettyId(replyToNote.signatures[0], true)} {' – '} @@ -341,8 +343,7 @@ export default function ChatEditorForm({ className="btn btn-sm btn-primary" disabled={!message || !message.trim() || loading} > - Send{' '} - + Send
diff --git a/components/forum/ChatReply.js b/components/forum/ChatReply.js index c4107636d..dbc664974 100644 --- a/components/forum/ChatReply.js +++ b/components/forum/ChatReply.js @@ -102,7 +102,7 @@ const ChatReply = ({ setLoading(false) }) .catch((err) => { - promptError(err.message, { scrollToTop: false }) + promptError(err.message) setLoading(false) }) @@ -138,7 +138,7 @@ const ChatReply = ({ setLoading(false) }) .catch((err) => { - promptError(err.message, { scrollToTop: false }) + promptError(err.message) setLoading(false) }) } @@ -149,7 +149,7 @@ const ChatReply = ({ copy( `${window.location.origin}${window.location.pathname}?id=${note.forum}¬eId=${note.id}${window.location.hash}` ) - promptMessage('Reply URL copied to clipboard', { scrollToTop: false }) + promptMessage('Reply URL copied to clipboard') } useEffect(() => { diff --git a/components/forum/FilterForm.js b/components/forum/FilterForm.js index 135ecee43..3a092dd4c 100644 --- a/components/forum/FilterForm.js +++ b/components/forum/FilterForm.js @@ -85,7 +85,7 @@ export default function FilterForm({ urlParams )}` ) - promptMessage('Forum URL copied to clipboard', { scrollToTop: false }) + promptMessage('Forum URL copied to clipboard') } return ( diff --git a/components/forum/ForumReply.js b/components/forum/ForumReply.js index def43d795..914d502e6 100644 --- a/components/forum/ForumReply.js +++ b/components/forum/ForumReply.js @@ -457,7 +457,7 @@ function CopyLinkButton({ forumId, noteId }) { if (!window.location) return copy(`${window.location.origin}${window.location.pathname}?id=${forumId}¬eId=${noteId}`) - promptMessage('Reply URL copied to clipboard', { scrollToTop: false }) + promptMessage('Reply URL copied to clipboard') } return ( diff --git a/components/group/GroupContent.js b/components/group/GroupContent.js index 895645c82..22c10ebdd 100644 --- a/components/group/GroupContent.js +++ b/components/group/GroupContent.js @@ -35,7 +35,7 @@ export default function GroupContent({ group, accessToken, profileId, reloadGrou invitation: group.domain ? `${group.domain}/-/Edit` : group.invitations[0], } await api.post('/groups/edits', requestBody, { accessToken }) - promptMessage(`Content object for ${group.id} has been updated`, { scrollToTop: false }) + promptMessage(`Content object for ${group.id} has been updated`) reloadGroup() } catch (error) { let { message } = error diff --git a/components/group/GroupContentScripts.js b/components/group/GroupContentScripts.js index 3cd2f3763..8bbba29a9 100644 --- a/components/group/GroupContentScripts.js +++ b/components/group/GroupContentScripts.js @@ -9,12 +9,7 @@ import SpinnerButton from '../SpinnerButton' import api from '../../lib/api-client' import { prettyField } from '../../lib/utils' -export default function GroupContentScripts({ - group, - profileId, - accessToken, - reloadGroup, -}) { +export default function GroupContentScripts({ group, profileId, accessToken, reloadGroup }) { const contentScripts = Object.keys(group.content ?? {}).filter( (key) => key.endsWith('_script') && typeof group.content[key].value === 'string' ) @@ -63,7 +58,7 @@ function GroupCodeEditor({ group, fieldName, profileId, accessToken, reloadGroup id: group.id, content: { ...group.content, - [fieldName]: { value: code } + [fieldName]: { value: code }, }, }, readers: [profileId], @@ -72,7 +67,7 @@ function GroupCodeEditor({ group, fieldName, profileId, accessToken, reloadGroup invitation: group.domain ? `${group.domain}/-/Edit` : group.invitations[0], } await api.post('/groups/edits', requestBody, { accessToken }) - promptMessage(`Content object for ${group.id} has been updated`, { scrollToTop: false }) + promptMessage(`Content object for ${group.id} has been updated`) reloadGroup() } catch (error) { promptError(error.message) @@ -82,11 +77,7 @@ function GroupCodeEditor({ group, fieldName, profileId, accessToken, reloadGroup return (
- +
{ payload: { newMembers, existingDeleted }, }) getMemberAnonIds() - promptMessage( - `${newMembersMessage} ${existingDeletedMessage} ${existingActiveMessage}`, - { scrollToTop: false } - ) + promptMessage(`${newMembersMessage} ${existingDeletedMessage} ${existingActiveMessage}`) reloadGroup() } catch (error) { promptError(error.message) @@ -547,11 +544,9 @@ const GroupMembers = ({ group, accessToken, reloadGroup }) => { const selectedMemberIds = groupMembers.filter((p) => p.isSelected).map((q) => q.id) const success = copy(selectedMemberIds.join(',')) if (success) { - promptMessage(`${selectedMemberIds.length} IDs copied to clipboard`, { - scrollToTop: false, - }) + promptMessage(`${selectedMemberIds.length} IDs copied to clipboard`) } else { - promptError('Could not copy selected member IDs to clipboard', { scrollToTop: false }) + promptError('Could not copy selected member IDs to clipboard') } } diff --git a/components/group/GroupUICode.js b/components/group/GroupUICode.js index e93fd7b3f..823734716 100644 --- a/components/group/GroupUICode.js +++ b/components/group/GroupUICode.js @@ -38,7 +38,7 @@ const GroupUICode = ({ group, profileId, accessToken, reloadGroup }) => { } await api.post('/groups', groupToPost, { accessToken, version: 1 }) } - promptMessage(`UI code for ${group.id} has been updated`, { scrollToTop: false }) + promptMessage(`UI code for ${group.id} has been updated`) setShowCodeEditor(false) reloadGroup() } catch (error) { diff --git a/components/invitation/DateProcessesEditor.js b/components/invitation/DateProcessesEditor.js index 6c6d1cb9a..8e545c52b 100644 --- a/components/invitation/DateProcessesEditor.js +++ b/components/invitation/DateProcessesEditor.js @@ -416,12 +416,10 @@ const DateProcessesEditor = ({ ...(!isMetaInvitation && { invitations: metaInvitationId }), } await api.post(requestPath, requestBody, { accessToken }) - promptMessage(`Date processes for ${prettyId(invitation.id)} updated`, { - scrollToTop: false, - }) + promptMessage(`Date processes for ${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } setIsSaving(false) diff --git a/components/invitation/InvitationCode.js b/components/invitation/InvitationCode.js index 52c5c69ff..ad06245da 100644 --- a/components/invitation/InvitationCode.js +++ b/components/invitation/InvitationCode.js @@ -36,10 +36,10 @@ const InvitationCode = ({ invitation, accessToken, loadInvitation, codeType }) = accessToken, version: 1, }) - promptMessage(`Code for ${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Code for ${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } setIsSaving(false) @@ -145,10 +145,10 @@ export const InvitationCodeV2 = ({ ...(!isMetaInvitation && { invitations: metaInvitationId }), } await api.post('/invitations/edits', requestBody, { accessToken }) - promptMessage(`Code for ${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Code for ${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } setIsSaving(false) diff --git a/components/invitation/InvitationGeneral.js b/components/invitation/InvitationGeneral.js index ed4036e5c..2a747e37d 100644 --- a/components/invitation/InvitationGeneral.js +++ b/components/invitation/InvitationGeneral.js @@ -348,11 +348,11 @@ const InvitationGeneralEdit = ({ invitation, accessToken, loadInvitation, setIsE const requestBody = constructInvitationToPost() try { await api.post('/invitations', requestBody, { accessToken, version: 1 }) - promptMessage(`Settings for ${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Settings for ${prettyId(invitation.id)} updated`) setIsEditMode(false) loadInvitation(invitation.id) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } setIsSaving(false) } @@ -672,11 +672,11 @@ const InvitationGeneralEditV2 = ({ throw new Error('No meta invitation found') } await api.post('/invitations/edits', requestBody, { accessToken }) - promptMessage(`Settings for ${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Settings for ${prettyId(invitation.id)} updated`) setIsEditMode(false) loadInvitation(invitation.id) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } setIsSaving(false) } diff --git a/components/invitation/InvitationReply.js b/components/invitation/InvitationReply.js index f0606cfbb..c30866898 100644 --- a/components/invitation/InvitationReply.js +++ b/components/invitation/InvitationReply.js @@ -60,14 +60,14 @@ export default function InvitationReply({ accessToken, version: 1, }) - promptMessage(`Settings for '${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Settings for '${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { let { message } = error if (error instanceof SyntaxError) { message = `Reply content is not valid JSON - ${error.message}. Make sure all quotes and brackets match.` } - promptError(message, { scrollToTop: false }) + promptError(message) } setIsSaving(false) } @@ -166,14 +166,14 @@ export function InvitationReplyV2({ const replyObj = JSON.parse(cleanReplyString.length ? cleanReplyString : '[]') const requestBody = getRequestBody(replyObj) await api.post('/invitations/edits', requestBody, { accessToken }) - promptMessage(`Settings for ${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Settings for ${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { let { message } = error if (error instanceof SyntaxError) { message = `Reply is not valid JSON: ${error.message}. Make sure all quotes and brackets match.` } - promptError(message, { scrollToTop: false }) + promptError(message) } setIsSaving(false) } @@ -236,7 +236,7 @@ export function InvitationReplyWithPreview({ invitation, accessToken, loadInvita rdate: undefined, } } catch (error) { - promptError(`Reply is not valid JSON: ${error.message}.`, { scrollToTop: false }) + promptError(`Reply is not valid JSON: ${error.message}.`) } return {} } @@ -249,14 +249,14 @@ export function InvitationReplyWithPreview({ invitation, accessToken, loadInvita accessToken, version: 1, }) - promptMessage(`Settings for '${prettyId(invitation.id)} updated`, { scrollToTop: false }) + promptMessage(`Settings for '${prettyId(invitation.id)} updated`) loadInvitation(invitation.id) } catch (error) { let { message } = error if (error instanceof SyntaxError) { message = `Reply is not valid JSON: ${error.message}. Make sure all quotes and brackets match.` } - promptError(message, { scrollToTop: false }) + promptError(message) } setIsSaving(false) } diff --git a/components/profile/NameSection.js b/components/profile/NameSection.js index cb795bd47..dfdcab5d6 100644 --- a/components/profile/NameSection.js +++ b/components/profile/NameSection.js @@ -188,7 +188,7 @@ const NamesSection = ({ profileNames, updateNames, preferredUsername }) => { data: { key, field: 'username', value: tildeUsername.username }, }) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } }, 800), [] diff --git a/components/profile/PersonalLinksSection.js b/components/profile/PersonalLinksSection.js index e7ae06be7..97f420e9f 100644 --- a/components/profile/PersonalLinksSection.js +++ b/components/profile/PersonalLinksSection.js @@ -14,7 +14,7 @@ const PersonalLinkInput = ({ type, links, setLinks }) => { case 'gscholar': { const isValid = value.startsWith('https://scholar.google') if (!isValid) { - promptError(`${value} is not a valid Google Scholar URL`, { scrollToTop: false }) + promptError(`${value} is not a valid Google Scholar URL`) } setLinks({ type, data: { value, valid: isValid } }) break @@ -22,7 +22,7 @@ const PersonalLinkInput = ({ type, links, setLinks }) => { case 'semanticScholar': { const isValid = /^https:\/\/www\.semanticscholar\.org/.test(value) if (!isValid) { - promptError(`${value} is not a valid Semantic Scholar URL`, { scrollToTop: false }) + promptError(`${value} is not a valid Semantic Scholar URL`) } setLinks({ type, data: { value, valid: isValid } }) break @@ -30,7 +30,7 @@ const PersonalLinkInput = ({ type, links, setLinks }) => { case 'aclanthology': { const isValid = /^https:\/\/aclanthology\.org\/people\/.+$/.test(value) if (!isValid) { - promptError(`${value} is not a valid ACL Anthology URL`, { scrollToTop: false }) + promptError(`${value} is not a valid ACL Anthology URL`) } setLinks({ type, data: { value, valid: isValid } }) break @@ -38,7 +38,7 @@ const PersonalLinkInput = ({ type, links, setLinks }) => { default: { const isValid = isValidURL(value) if (!isValid) { - promptError(`${value} is not a valid ${type} URL`, { scrollToTop: false }) + promptError(`${value} is not a valid ${type} URL`) } setLinks({ type, data: { value, valid: isValid } }) } diff --git a/components/webfield/NoteMetaReviewStatus.js b/components/webfield/NoteMetaReviewStatus.js index 8c1fa4ed4..6e93b89bb 100644 --- a/components/webfield/NoteMetaReviewStatus.js +++ b/components/webfield/NoteMetaReviewStatus.js @@ -213,7 +213,7 @@ export const ProgramChairConsolePaperAreaChairProgress = ({ const getACSACEmail = async (preferredName, profileId) => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -224,9 +224,9 @@ export const ProgramChairConsolePaperAreaChairProgress = ({ const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${preferredName} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } diff --git a/components/webfield/NoteReviewStatus.js b/components/webfield/NoteReviewStatus.js index 50f677ebe..95d792293 100644 --- a/components/webfield/NoteReviewStatus.js +++ b/components/webfield/NoteReviewStatus.js @@ -168,9 +168,7 @@ Click on the link below to go to the ${prettyField( localStorage.setItem(`${forumUrl}|${reviewer.reviewerProfileId}`, Date.now()) setUpdateLastSent((p) => !p) $(`#reviewer-reminder-${reviewer.anonymousId}`).modal('hide') - promptMessage(`A reminder email has been sent to ${reviewer.preferredName}`, { - scrollToTop: false, - }) + promptMessage(`A reminder email has been sent to ${reviewer.preferredName}`) } catch (apiError) { setError(apiError.message) } @@ -256,7 +254,7 @@ export const AcPcConsoleReviewerStatusRow = ({ } const getReviewerEmail = async () => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -267,9 +265,9 @@ export const AcPcConsoleReviewerStatusRow = ({ const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${reviewer.preferredName} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } return ( diff --git a/components/webfield/ProgramChairConsole/AreaChairStatus.js b/components/webfield/ProgramChairConsole/AreaChairStatus.js index 248302303..a6d04ff8e 100644 --- a/components/webfield/ProgramChairConsole/AreaChairStatus.js +++ b/components/webfield/ProgramChairConsole/AreaChairStatus.js @@ -45,7 +45,7 @@ const CommitteeSummary = ({ rowData, bidEnabled, recommendationEnabled, invitati const getACSACEmail = async (name, profileId) => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -56,9 +56,9 @@ const CommitteeSummary = ({ rowData, bidEnabled, recommendationEnabled, invitati const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${name} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } diff --git a/components/webfield/ProgramChairConsole/ReviewerStatus.js b/components/webfield/ProgramChairConsole/ReviewerStatus.js index 286807573..bed561603 100644 --- a/components/webfield/ProgramChairConsole/ReviewerStatus.js +++ b/components/webfield/ProgramChairConsole/ReviewerStatus.js @@ -32,7 +32,7 @@ const ReviewerSummary = ({ rowData, bidEnabled, invitations }) => { ) const getReviewerEmail = async (name, profileId) => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -43,9 +43,9 @@ const ReviewerSummary = ({ rowData, bidEnabled, invitations }) => { const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${name} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } return ( diff --git a/components/webfield/ProgramChairConsole/SeniorAreaChairStatus.js b/components/webfield/ProgramChairConsole/SeniorAreaChairStatus.js index e46ca5c7c..fca3ca96c 100644 --- a/components/webfield/ProgramChairConsole/SeniorAreaChairStatus.js +++ b/components/webfield/ProgramChairConsole/SeniorAreaChairStatus.js @@ -18,7 +18,7 @@ const BasicProfileSummary = ({ profile, profileId }) => { const getEmail = async () => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -29,9 +29,9 @@ const BasicProfileSummary = ({ profile, profileId }) => { const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${preferredName} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } return ( @@ -102,7 +102,7 @@ const SeniorAreaChairStatusRowForDirectPaperAssignment = ({ const getEmail = async () => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -113,9 +113,9 @@ const SeniorAreaChairStatusRowForDirectPaperAssignment = ({ const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${preferredName} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } diff --git a/components/webfield/SeniorAreaChairConsole/AreaChairStatus.js b/components/webfield/SeniorAreaChairConsole/AreaChairStatus.js index a9bdc4755..298613022 100644 --- a/components/webfield/SeniorAreaChairConsole/AreaChairStatus.js +++ b/components/webfield/SeniorAreaChairConsole/AreaChairStatus.js @@ -44,7 +44,7 @@ const CommitteeSummary = ({ rowData }) => { const getACEmail = async () => { if (!preferredEmailInvitationId) { - promptError('Email is not available.', { scrollToTop: false }) + promptError('Email is not available.') return } try { @@ -55,9 +55,9 @@ const CommitteeSummary = ({ rowData }) => { const email = result.edges?.[0]?.tail if (!email) throw new Error('Email is not available.') copy(`${preferredName} <${email}>`) - promptMessage(`${email} copied to clipboard`, { scrollToTop: false }) + promptMessage(`${email} copied to clipboard`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } diff --git a/hooks/usePrompt.js b/hooks/usePrompt.js index fcb2fba4a..b0111bfaa 100644 --- a/hooks/usePrompt.js +++ b/hooks/usePrompt.js @@ -16,11 +16,7 @@ export default function usePrompt() { { maxCount: 2, ...(canClose && { - closeIcon: ( - - ), + closeIcon: , }), }, (notification) => { @@ -29,30 +25,30 @@ export default function usePrompt() { ) }, []) return { - promptMessage: (message) => + promptMessage: (message, customDuration) => notificationInstance?.notice({ content: (
), - duration: messageDuration, + duration: customDuration ?? messageDuration, closable: canClose, pauseOnHover: true, }), - promptError: (message) => + promptError: (message, customDuration) => notificationInstance?.notice({ content: (
), - duration: errorDuration, + duration: customDuration ?? errorDuration, closable: canClose, pauseOnHover: true, }), - promptLogin: () => + promptLogin: (customDuration) => notificationInstance?.notice({ content: (
), - duration: errorDuration, + duration: customDuration ?? errorDuration, closable: canClose, pauseOnHover: true, }), @@ -78,11 +74,7 @@ export default function usePrompt() { { maxCount: 2, ...(canClose && { - closeIcon: ( - - ), + closeIcon: , }), }, (notification) => { diff --git a/pages/login.js b/pages/login.js index 5c85d6a47..cfa05dada 100644 --- a/pages/login.js +++ b/pages/login.js @@ -37,7 +37,7 @@ const LoginForm = () => { promptMessage( `A confirmation email with the subject "OpenReview signup confirmation" has been sent to ${email}. Please click the link in this email to confirm your email address and complete registration.`, - { noTimeout: true } + 8 ) } catch (error) { setLoginError(error) diff --git a/pages/profile/activate.js b/pages/profile/activate.js index effc96075..44d80819b 100644 --- a/pages/profile/activate.js +++ b/pages/profile/activate.js @@ -46,9 +46,7 @@ const ActivateProfile = ({ appContext }) => { content: newProfileData, }) if (token) { - promptMessage('Your OpenReview profile has been successfully created', { - scrollToTop: false, - }) + promptMessage('Your OpenReview profile has been successfully created') loginUser(user, token) } else { // If user moderation is enabled, PUT /activate/${token} will return an empty response diff --git a/pages/profile/edit.js b/pages/profile/edit.js index 2be0053c4..38874b275 100644 --- a/pages/profile/edit.js +++ b/pages/profile/edit.js @@ -129,9 +129,7 @@ export default function ProfileEdit({ appContext }) { unlinkPublication(profile.id, publicationId) ) ) - promptMessage('Your profile information has been successfully updated', { - timeout: 2000, - }) + promptMessage('Your profile information has been successfully updated', 2) loadProfile() } catch (apiError) { promptError(apiError.message) diff --git a/pages/signup.js b/pages/signup.js index 8089ea31f..6a9e561df 100644 --- a/pages/signup.js +++ b/pages/signup.js @@ -397,9 +397,7 @@ const ClaimProfileForm = ({ id, registerUser }) => { if (!emailVisible) { if (!validateFullName()) { - promptError('Your name must match the name of the profile you are claiming', { - scrollToTop: false, - }) + promptError('Your name must match the name of the profile you are claiming') return } setEmailVisible(true) diff --git a/pages/user/moderation.js b/pages/user/moderation.js index ae4b49fd2..f7ed458ec 100644 --- a/pages/user/moderation.js +++ b/pages/user/moderation.js @@ -1755,7 +1755,7 @@ const UserModerationQueue = ({ setTotalCount(result.count ?? 0) setProfiles(result.profiles ?? []) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } @@ -1789,9 +1789,9 @@ const UserModerationQueue = ({ setPageNumber((p) => p - 1) } reload() - promptMessage(`${prettyId(profileId)} is now active`, { scrollToTop: false }) + promptMessage(`${prettyId(profileId)} is now active`) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) setIdsLoading((p) => p.filter((q) => q !== profileId)) } } @@ -1848,7 +1848,7 @@ const UserModerationQueue = ({ } reload() } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } } @@ -1884,7 +1884,7 @@ const UserModerationQueue = ({ setPageNumber((p) => p - 1) } } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } reload() } @@ -1923,7 +1923,7 @@ const UserModerationQueue = ({ { accessToken } ) } catch (error) { - promptError(error.message, { scrollToTop: false }) + promptError(error.message) } reload() } diff --git a/styles/global.scss b/styles/global.scss index 29ecfc04b..d4e2ed1b8 100644 --- a/styles/global.scss +++ b/styles/global.scss @@ -1003,13 +1003,15 @@ header { .rc-notification-notice-close { pointer-events: all; - margin-left: -2.25rem; margin-right: 0; color: darkgray; cursor: pointer; &:focus { outline: none; } + &:hover { + color: black; + } } } } diff --git a/tests/registerPage.ts b/tests/registerPage.ts index 82c1af541..33f0381e9 100644 --- a/tests/registerPage.ts +++ b/tests/registerPage.ts @@ -225,9 +225,9 @@ test('enter invalid name', async (t) => { .eql( 'Error: The name 1 is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' ) - .typeText(fullNameInputSelector, 'abc') + .typeText(fullNameInputSelector, 'abc', { replace: true }) .expect(messageSelector.exists).notOk() // page calls clearMessage - .typeText(fullNameInputSelector, 'abc `') + .typeText(fullNameInputSelector, 'abc `', { replace: true }) .expect(messageSelector.innerText) .eql( 'Error: The name ` is invalid. Only letters, single hyphens, single dots at the end of a name, and single spaces are allowed' diff --git a/unitTests/FileUploadWidget.test.js b/unitTests/FileUploadWidget.test.js index 47db94b75..9d6914d16 100644 --- a/unitTests/FileUploadWidget.test.js +++ b/unitTests/FileUploadWidget.test.js @@ -120,10 +120,7 @@ describe('FileUploadWidget', () => { Object.defineProperty(file, 'size', { value: 1024 * 1000 * 10 + 1 }) await userEvent.upload(fileInput, file) - expect(promptError).toHaveBeenCalledWith( - expect.stringContaining('File is too large.'), - expect.anything() - ) + expect(promptError).toHaveBeenCalledWith(expect.stringContaining('File is too large.')) }) test('clear value when user delete file', async () => {