From 6a2754d4fbf61c78649a13e30a5af21b049b26cc Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 12 Mar 2024 02:50:06 +0530 Subject: [PATCH] feat: refactor and improve notifications implementation --- package-lock.json | 183 ++++++++---------- packages/bruno-app/.env.production | 4 +- .../bruno-app/src/components/Modal/index.js | 16 +- .../components/Notifications/StyleWrapper.js | 94 +++++---- .../src/components/Notifications/index.js | 69 ++++--- .../bruno-app/src/components/Sidebar/index.js | 2 +- packages/bruno-app/src/globalStyles.js | 12 -- .../src/providers/ReduxStore/index.js | 4 +- .../src/providers/ReduxStore/slices/app.js | 98 +--------- .../ReduxStore/slices/notifications.js | 106 ++++++++++ packages/bruno-app/src/themes/dark.js | 34 +--- packages/bruno-app/src/themes/light.js | 39 +--- packages/bruno-app/src/utils/common/index.js | 5 +- .../src/utils/common/notifications.js | 19 -- .../bruno-electron/src/app/menu-template.js | 8 +- 15 files changed, 301 insertions(+), 392 deletions(-) create mode 100644 packages/bruno-app/src/providers/ReduxStore/slices/notifications.js delete mode 100644 packages/bruno-app/src/utils/common/notifications.js diff --git a/package-lock.json b/package-lock.json index c2ba278cb..06a53db40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ }, "node_modules/@ampproject/remapping": { "version": "2.2.1", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -745,6 +746,7 @@ }, "node_modules/@babel/compat-data": { "version": "7.23.5", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -752,6 +754,7 @@ }, "node_modules/@babel/core": { "version": "7.23.9", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -814,6 +817,7 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.23.5", @@ -828,6 +832,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -835,6 +840,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", + "dev": true, "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { @@ -941,6 +947,7 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.23.3", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1008,6 +1015,7 @@ }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" @@ -1053,6 +1061,7 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.23.5", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1073,6 +1082,7 @@ }, "node_modules/@babel/helpers": { "version": "7.23.9", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.23.9", @@ -4205,23 +4215,6 @@ "node": ">=12" } }, - "node_modules/@n8n/vm2": { - "version": "3.9.23", - "resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.23.tgz", - "integrity": "sha512-yu+It+L89uljQsCJ2e9cQaXzoXJe9bU69QQIoWUOcUw0u5Zon37DuB7bdNNsjKS1ZdFD+fBWCQpq/FkqHsSjXQ==", - "peer": true, - "dependencies": { - "acorn": "^8.7.0", - "acorn-walk": "^8.2.0" - }, - "bin": { - "vm2": "bin/vm2" - }, - "engines": { - "node": ">=18.10", - "pnpm": ">=8.6.12" - } - }, "node_modules/@next/env": { "version": "12.3.3", "license": "MIT" @@ -6715,6 +6708,7 @@ }, "node_modules/browserslist": { "version": "4.22.3", + "dev": true, "funding": [ { "type": "opencollective", @@ -7652,6 +7646,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -8809,6 +8804,7 @@ }, "node_modules/electron-to-chromium": { "version": "1.4.667", + "dev": true, "license": "ISC" }, "node_modules/electron-util": { @@ -9812,6 +9808,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -13434,6 +13431,7 @@ }, "node_modules/node-releases": { "version": "2.0.14", + "dev": true, "license": "MIT" }, "node_modules/node-vault": { @@ -16387,6 +16385,7 @@ }, "node_modules/semver": { "version": "6.3.1", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17995,6 +17994,7 @@ }, "node_modules/update-browserslist-db": { "version": "1.0.13", + "dev": true, "funding": [ { "type": "opencollective", @@ -19009,6 +19009,7 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "inquirer": "^9.1.4", + "json-bigint": "^1.0.0", "lodash": "^4.17.21", "mustache": "^4.2.0", "qs": "^6.11.0", @@ -19252,6 +19253,7 @@ }, "@ampproject/remapping": { "version": "2.2.1", + "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -19805,10 +19807,12 @@ } }, "@babel/compat-data": { - "version": "7.23.5" + "version": "7.23.5", + "dev": true }, "@babel/core": { "version": "7.23.9", + "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -19851,6 +19855,7 @@ }, "@babel/helper-compilation-targets": { "version": "7.23.6", + "dev": true, "requires": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -19861,12 +19866,14 @@ "dependencies": { "lru-cache": { "version": "5.1.1", + "dev": true, "requires": { "yallist": "^3.0.2" } }, "yallist": { - "version": "3.1.1" + "version": "3.1.1", + "dev": true } } }, @@ -19936,6 +19943,7 @@ }, "@babel/helper-module-transforms": { "version": "7.23.3", + "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -19974,6 +19982,7 @@ }, "@babel/helper-simple-access": { "version": "7.22.5", + "dev": true, "requires": { "@babel/types": "^7.22.5" } @@ -19998,7 +20007,8 @@ "version": "7.22.20" }, "@babel/helper-validator-option": { - "version": "7.23.5" + "version": "7.23.5", + "dev": true }, "@babel/helper-wrap-function": { "version": "7.22.20", @@ -20011,6 +20021,7 @@ }, "@babel/helpers": { "version": "7.23.9", + "dev": true, "requires": { "@babel/template": "^7.23.9", "@babel/traverse": "^7.23.9", @@ -20088,8 +20099,7 @@ }, "@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", - "dev": true, - "requires": {} + "dev": true }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -21102,8 +21112,7 @@ "version": "3.0.4" }, "ws": { - "version": "8.13.0", - "requires": {} + "version": "8.13.0" } } }, @@ -21131,8 +21140,7 @@ }, "dependencies": { "ws": { - "version": "8.13.0", - "requires": {} + "version": "8.13.0" } } }, @@ -21256,8 +21264,7 @@ } }, "@graphql-typed-document-node/core": { - "version": "3.2.0", - "requires": {} + "version": "3.2.0" }, "@iarna/toml": { "version": "2.2.5" @@ -22016,16 +22023,6 @@ "@n1ru4l/push-pull-async-iterable-iterator": { "version": "3.2.0" }, - "@n8n/vm2": { - "version": "3.9.23", - "resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.23.tgz", - "integrity": "sha512-yu+It+L89uljQsCJ2e9cQaXzoXJe9bU69QQIoWUOcUw0u5Zon37DuB7bdNNsjKS1ZdFD+fBWCQpq/FkqHsSjXQ==", - "peer": true, - "requires": { - "acorn": "^8.7.0", - "acorn-walk": "^8.2.0" - } - }, "@next/env": { "version": "12.3.3" }, @@ -22597,8 +22594,7 @@ } }, "@tabler/icons": { - "version": "1.119.0", - "requires": {} + "version": "1.119.0" }, "@tippyjs/react": { "version": "4.2.6", @@ -23026,6 +23022,7 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "inquirer": "^9.1.4", + "json-bigint": "^1.0.0", "lodash": "^4.17.21", "mustache": "^4.2.0", "qs": "^6.11.0", @@ -23138,8 +23135,7 @@ } }, "@usebruno/schema": { - "version": "file:packages/bruno-schema", - "requires": {} + "version": "file:packages/bruno-schema" }, "@usebruno/tests": { "version": "file:packages/bruno-tests", @@ -23283,8 +23279,7 @@ }, "@webpack-cli/configtest": { "version": "1.2.0", - "dev": true, - "requires": {} + "dev": true }, "@webpack-cli/info": { "version": "1.5.0", @@ -23295,8 +23290,7 @@ }, "@webpack-cli/serve": { "version": "1.7.0", - "dev": true, - "requires": {} + "dev": true }, "@whatwg-node/events": { "version": "0.0.3" @@ -23356,8 +23350,7 @@ }, "acorn-import-assertions": { "version": "1.9.0", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.3.2" @@ -23399,8 +23392,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "dev": true, - "requires": {} + "dev": true }, "amdefine": { "version": "0.0.8" @@ -24005,6 +23997,7 @@ }, "browserslist": { "version": "4.22.3", + "dev": true, "requires": { "caniuse-lite": "^1.0.30001580", "electron-to-chromium": "^1.4.648", @@ -24043,7 +24036,7 @@ "https-proxy-agent": "^7.0.2", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.0", - "json-bigint": "*", + "json-bigint": "^1.0.0", "lodash": "^4.17.21", "mime-types": "^2.1.35", "mustache": "^4.2.0", @@ -24297,8 +24290,7 @@ } }, "chai-string": { - "version": "1.5.0", - "requires": {} + "version": "1.5.0" }, "chalk": { "version": "3.0.0", @@ -24641,7 +24633,8 @@ "version": "1.0.5" }, "convert-source-map": { - "version": "2.0.0" + "version": "2.0.0", + "dev": true }, "cookie": { "version": "0.6.0" @@ -24759,8 +24752,7 @@ }, "css-declaration-sorter": { "version": "6.4.1", - "dev": true, - "requires": {} + "dev": true }, "css-loader": { "version": "6.10.0", @@ -24897,8 +24889,7 @@ }, "cssnano-utils": { "version": "3.1.0", - "dev": true, - "requires": {} + "dev": true }, "csso": { "version": "4.2.0", @@ -24956,8 +24947,7 @@ }, "dedent": { "version": "1.5.1", - "dev": true, - "requires": {} + "dev": true }, "deep-eql": { "version": "4.1.3", @@ -25390,7 +25380,8 @@ } }, "electron-to-chromium": { - "version": "1.4.667" + "version": "1.4.667", + "dev": true }, "electron-util": { "version": "0.17.2", @@ -26021,7 +26012,8 @@ } }, "gensync": { - "version": "1.0.0-beta.2" + "version": "1.0.0-beta.2", + "dev": true }, "get-caller-file": { "version": "2.0.5" @@ -26178,8 +26170,7 @@ } }, "goober": { - "version": "2.1.14", - "requires": {} + "version": "2.1.14" }, "gopd": { "version": "1.0.1", @@ -26347,8 +26338,7 @@ } }, "graphql-ws": { - "version": "5.12.1", - "requires": {} + "version": "5.12.1" }, "handlebars": { "version": "4.7.8", @@ -26620,8 +26610,7 @@ }, "icss-utils": { "version": "5.1.0", - "dev": true, - "requires": {} + "dev": true }, "idb": { "version": "7.1.1" @@ -26924,8 +26913,7 @@ "version": "3.0.1" }, "isomorphic-ws": { - "version": "5.0.0", - "requires": {} + "version": "5.0.0" }, "isstream": { "version": "0.1.2" @@ -27298,8 +27286,7 @@ }, "jest-pnp-resolver": { "version": "1.2.3", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.6.3", @@ -28038,8 +28025,7 @@ "version": "1.0.1" }, "merge-refs": { - "version": "1.2.2", - "requires": {} + "version": "1.2.2" }, "merge-stream": { "version": "2.0.0", @@ -28049,8 +28035,7 @@ "version": "1.4.1" }, "meros": { - "version": "1.3.0", - "requires": {} + "version": "1.3.0" }, "methods": { "version": "1.1.2" @@ -28297,7 +28282,8 @@ "version": "1.1.12" }, "node-releases": { - "version": "2.0.14" + "version": "2.0.14", + "dev": true }, "node-vault": { "version": "0.10.2", @@ -28910,23 +28896,19 @@ }, "postcss-discard-comments": { "version": "5.1.2", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-duplicates": { "version": "5.1.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-empty": { "version": "5.1.1", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-overridden": { "version": "5.1.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-import": { "version": "15.1.0", @@ -29011,8 +28993,7 @@ }, "postcss-modules-extract-imports": { "version": "3.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.4", @@ -29039,8 +29020,7 @@ }, "postcss-normalize-charset": { "version": "5.1.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -29479,8 +29459,7 @@ } }, "react-inspector": { - "version": "6.0.2", - "requires": {} + "version": "6.0.2" }, "react-is": { "version": "16.13.1" @@ -29630,8 +29609,7 @@ } }, "redux-thunk": { - "version": "2.4.2", - "requires": {} + "version": "2.4.2" }, "regenerate": { "version": "1.4.2", @@ -29950,8 +29928,7 @@ }, "rollup-plugin-peer-deps-external": { "version": "2.2.4", - "dev": true, - "requires": {} + "dev": true }, "rollup-plugin-postcss": { "version": "4.0.2", @@ -30093,7 +30070,8 @@ } }, "semver": { - "version": "6.3.1" + "version": "6.3.1", + "devOptional": true }, "semver-compare": { "version": "1.0.0", @@ -30509,8 +30487,7 @@ }, "style-loader": { "version": "3.3.4", - "dev": true, - "requires": {} + "dev": true }, "style-mod": { "version": "4.1.0" @@ -30542,8 +30519,7 @@ } }, "styled-jsx": { - "version": "5.0.7", - "requires": {} + "version": "5.0.7" }, "stylehacks": { "version": "5.1.1", @@ -31094,6 +31070,7 @@ }, "update-browserslist-db": { "version": "1.0.13", + "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -31183,8 +31160,7 @@ "version": "8.0.2" }, "use-sync-external-store": { - "version": "1.2.0", - "requires": {} + "version": "1.2.0" }, "utf8-byte-length": { "version": "1.0.4", @@ -31465,8 +31441,7 @@ } }, "ws": { - "version": "8.16.0", - "requires": {} + "version": "8.16.0" }, "xdg-basedir": { "version": "4.0.0", diff --git a/packages/bruno-app/.env.production b/packages/bruno-app/.env.production index 9b58ad903..93106b822 100644 --- a/packages/bruno-app/.env.production +++ b/packages/bruno-app/.env.production @@ -1,5 +1,3 @@ ENV=production -NEXT_PUBLIC_ENV=prod - -NEXT_PUBLIC_BRUNO_SERVER_API=https://ada.grafnode.com/api \ No newline at end of file +NEXT_PUBLIC_ENV=prod \ No newline at end of file diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js index 9ad452fd8..49fcccf02 100644 --- a/packages/bruno-app/src/components/Modal/index.js +++ b/packages/bruno-app/src/components/Modal/index.js @@ -1,13 +1,9 @@ import React, { useEffect, useState } from 'react'; import StyledWrapper from './StyledWrapper'; -const ModalHeader = ({ title, handleCancel, headerContentComponent }) => ( +const ModalHeader = ({ title, handleCancel, customHeader }) => (
- {headerContentComponent ? ( - headerContentComponent - ) : ( - <>{title ?
{title}
: null} - )} + {customHeader ? customHeader : <>{title ?
{title}
: null}} {handleCancel ? (
handleCancel() : null}> × @@ -58,7 +54,7 @@ const ModalFooter = ({ const Modal = ({ size, title, - headerContentComponent, + customHeader, confirmText, cancelText, handleCancel, @@ -104,11 +100,7 @@ const Modal = ({ return ( onClick(e) : null}>
- closeModal({ type: 'icon' })} - headerContentComponent={headerContentComponent} - /> + closeModal({ type: 'icon' })} customHeader={customHeader} /> {children} props.theme.notifications.settings.bg}; + background-color: ${(props) => props.theme.notifications.bg}; } .notification-count { - position: absolute; - right: -10px; - top: -15px; - z-index: 10; - margin-right: 0.5rem; - background-color: ${(props) => props.theme.notifications.bell.count}; - border-radius: 50%; - padding: 2px 1px; - min-width: 20px; display: flex; + color: white; + position: absolute; + top: -0.625rem; + right: -0.5rem; + margin-right: 0.5rem; justify-content: center; - font-size: 10px; + font-size: 0.625rem; + border-radius: 50%; + background-color: ${(props) => props.theme.colors.text.yellow}; + border: solid 2px ${(props) => props.theme.sidebar.bg}; + min-width: 1.25rem; } - .bell { - animation: fade-and-pulse 1s ease-in-out 1s forwards; + button.mark-as-read { + font-weight: 400 !important; } - ul { - background-color: ${(props) => props.theme.notifications.settings.sidebar.bg}; - border-right: solid 1px ${(props) => props.theme.notifications.settings.sidebar.borderRight}; + ul.notifications { + background-color: ${(props) => props.theme.notifications.list.bg}; + border-right: solid 1px ${(props) => props.theme.notifications.list.borderRight}; min-height: 400px; height: 100%; max-height: 85vh; overflow-y: auto; - } - li { - min-width: 150px; - min-height: 5rem; - display: block; - position: relative; - cursor: pointer; - padding: 8px 10px; - border-left: solid 2px transparent; - border-bottom: solid 1px ${(props) => props.theme.notifications.settings.item.borderBottom}; - font-weight: 600; - &:hover { - background-color: ${(props) => props.theme.notifications.settings.item.hoverBg}; - } - } + li { + min-width: 150px; + cursor: pointer; + padding: 0.5rem 0.625rem; + border-left: solid 2px transparent; + color: ${(props) => props.theme.textLink}; + border-bottom: solid 1px ${(props) => props.theme.notifications.list.borderBottom}; + &:hover { + background-color: ${(props) => props.theme.notifications.list.hoverBg}; + } - .active { - font-weight: normal; - background-color: ${(props) => props.theme.notifications.settings.item.active.bg} !important; - border-left: solid 2px ${(props) => props.theme.notifications.settings.item.border}; - &:hover { - background-color: ${(props) => props.theme.notifications.settings.item.active.hoverBg} !important; - } - } + &.active { + color: ${(props) => props.theme.text} !important; + background-color: ${(props) => props.theme.notifications.list.active.bg} !important; + border-left: solid 2px ${(props) => props.theme.notifications.list.active.border}; + &:hover { + background-color: ${(props) => props.theme.notifications.list.active.hoverBg} !important; + } + } - .read { - opacity: 0.7; - font-weight: normal; - background-color: ${(props) => props.theme.notifications.settings.item.read.bg} !important; - &:hover { - background-color: ${(props) => props.theme.notifications.settings.item.read.hoverBg} !important; + &.read { + color: ${(props) => props.theme.text} !important; + } + + .notification-date { + font-size: 0.6875rem; + } } } .notification-title { - // text ellipses 2 lines - // white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; @@ -79,12 +73,12 @@ const StyledWrapper = styled.div` } .notification-date { - color: ${(props) => props.theme.notifications.settings.item.date.color} !important; + color: ${(props) => props.theme.colors.text.muted}; } .pagination { - background-color: ${(props) => props.theme.notifications.settings.sidebar.bg}; - border-right: solid 1px ${(props) => props.theme.notifications.settings.sidebar.borderRight}; + background-color: ${(props) => props.theme.notifications.list.bg}; + border-right: solid 1px ${(props) => props.theme.notifications.list.borderRight}; } `; diff --git a/packages/bruno-app/src/components/Notifications/index.js b/packages/bruno-app/src/components/Notifications/index.js index 6c6216f09..bb7df5b2f 100644 --- a/packages/bruno-app/src/components/Notifications/index.js +++ b/packages/bruno-app/src/components/Notifications/index.js @@ -5,25 +5,26 @@ import Modal from 'components/Modal/index'; import { useEffect } from 'react'; import { fetchNotifications, - markMultipleNotificationsAsRead, + markAllNotificationsAsRead, markNotificationAsRead -} from 'providers/ReduxStore/slices/app'; +} from 'providers/ReduxStore/slices/notifications'; import { useDispatch, useSelector } from 'react-redux'; -import { humanizeDate, relativeDate } from 'utils/common/index'; +import { humanizeDate, relativeDate } from 'utils/common'; + +const PAGE_SIZE = 5; const Notifications = () => { const dispatch = useDispatch(); - const notificationsById = useSelector((state) => state.app.notifications); - const notifications = [...notificationsById].reverse(); + const notifications = useSelector((state) => state.notifications.notifications); - const [showNotificationsModal, toggleNotificationsModal] = useState(false); + const [showNotificationsModal, setShowNotificationsModal] = useState(false); const [selectedNotification, setSelectedNotification] = useState(null); - const [pageSize, setPageSize] = useState(5); const [pageNumber, setPageNumber] = useState(1); - const notificationsStartIndex = (pageNumber - 1) * pageSize; - const notificationsEndIndex = pageNumber * pageSize; - const totalPages = Math.ceil(notifications.length / pageSize); + const notificationsStartIndex = (pageNumber - 1) * PAGE_SIZE; + const notificationsEndIndex = pageNumber * PAGE_SIZE; + const totalPages = Math.ceil(notifications.length / PAGE_SIZE); + const unreadNotifications = notifications.filter((notification) => !notification.read); useEffect(() => { dispatch(fetchNotifications()); @@ -62,22 +63,17 @@ const Notifications = () => { dispatch(markNotificationAsRead({ notificationId: notification?.id })); }; - const unreadNotifications = notifications.filter((notification) => !notification.read); - - const modalHeaderContentComponent = ( + const modalCustomHeader = (
NOTIFICATIONS
{unreadNotifications.length > 0 && ( <>
- {unreadNotifications.length} unread notifications + {unreadNotifications.length} unread notifications
@@ -92,7 +88,7 @@ const Notifications = () => { className="relative" onClick={() => { dispatch(fetchNotifications()); - toggleNotificationsModal(true); + setShowNotificationsModal(true); }} > {
{unreadNotifications.length}
)}
+ {showNotificationsModal && ( { - toggleNotificationsModal(false); + setShowNotificationsModal(false); }} handleCancel={() => { - toggleNotificationsModal(false); + setShowNotificationsModal(false); }} hideFooter={true} - headerContentComponent={modalHeaderContentComponent} + customHeader={modalCustomHeader} + disableCloseOnOutsideClick={true} + disableEscapeKey={true} >
{notifications?.length > 0 ? (
    {notifications?.slice(notificationsStartIndex, notificationsEndIndex)?.map((notification) => (
  • {notification?.title}
    - {/* human readable relative date */} -
    - {relativeDate(notification?.date)} -
    +
    {relativeDate(notification?.date)}
  • ))}
-
+
Page @@ -170,9 +167,11 @@ const Notifications = () => {
-
-
{selectedNotification?.title}
-
{humanizeDate(selectedNotification?.date)}
+
+
{selectedNotification?.title}
+
+ {humanizeDate(selectedNotification?.date)} +
{ @@ -20,7 +21,8 @@ export const store = configureStore({ reducer: { app: appReducer, collections: collectionsReducer, - tabs: tabsReducer + tabs: tabsReducer, + notifications: notificationsReducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware) }); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index 6c82690a7..f4dd7393d 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -1,8 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import filter from 'lodash/filter'; import toast from 'react-hot-toast'; -import { getReadNotificationIds, setReadNotificationsIds } from 'utils/common/notifications'; -import { getAppInstallDate } from 'utils/common/platform'; const initialState = { isDragging: false, @@ -26,10 +24,7 @@ const initialState = { } }, cookies: [], - taskQueue: [], - notifications: [], - fetchingNotifications: false, - readNotificationIds: getReadNotificationIds() || [] + taskQueue: [] }; export const appSlice = createSlice({ @@ -74,70 +69,6 @@ export const appSlice = createSlice({ }, removeAllTasksFromQueue: (state) => { state.taskQueue = []; - }, - fetchingNotifications: (state, action) => { - state.fetchingNotifications = action.payload.fetching; - }, - updateNotifications: (state, action) => { - let notifications = action.payload.notifications || []; - let readNotificationIds = state.readNotificationIds; - - // App installed date - let appInstalledOnDate = getAppInstallDate(); - - // date 5 days before - let dateFiveDaysBefore = new Date(); - dateFiveDaysBefore.setDate(dateFiveDaysBefore.getDate() - 5); - - // check if app was installed in the last 5 days - if (appInstalledOnDate > dateFiveDaysBefore) { - // filter out notifications that were sent before the app was installed - notifications = notifications.filter( - (notification) => new Date(notification.date) > new Date(appInstalledOnDate) - ); - } else { - // filter out notifications that sent within the last 5 days - notifications = notifications.filter( - (notification) => new Date(notification.date) > new Date(dateFiveDaysBefore) - ); - } - - state.notifications = notifications.map((notification) => { - return { - ...notification, - read: readNotificationIds.includes(notification.id) - }; - }); - }, - markNotificationAsRead: (state, action) => { - let readNotificationIds = state.readNotificationIds; - readNotificationIds.push(action.payload.notificationId); - state.readNotificationIds = readNotificationIds; - - // set the read notification ids in the localstorage - setReadNotificationsIds(readNotificationIds); - - state.notifications = state.notifications.map((notification) => { - return { - ...notification, - read: readNotificationIds.includes(notification.id) - }; - }); - }, - markMultipleNotificationsAsRead: (state, action) => { - let readNotificationIds = state.readNotificationIds; - readNotificationIds.push(...action.payload.notificationIds); - state.readNotificationIds = readNotificationIds; - - // set the read notification ids in the localstorage - setReadNotificationsIds(readNotificationIds); - - state.notifications = state.notifications.map((notification) => { - return { - ...notification, - read: readNotificationIds.includes(notification.id) - }; - }); } } }); @@ -155,12 +86,7 @@ export const { updateCookies, insertTaskIntoQueue, removeTaskFromQueue, - removeAllTasksFromQueue, - updateNotifications, - fetchingNotifications, - mergeNotifications, - markNotificationAsRead, - markMultipleNotificationsAsRead + removeAllTasksFromQueue } = appSlice.actions; export const savePreferences = (preferences) => (dispatch, getState) => { @@ -188,26 +114,6 @@ export const deleteCookiesForDomain = (domain) => (dispatch, getState) => { }); }; -export const fetchNotifications = () => (dispatch, getState) => { - return new Promise((resolve, reject) => { - const { ipcRenderer } = window; - dispatch(fetchingNotifications({ fetching: true })); - ipcRenderer - .invoke('renderer:fetch-notifications') - .then((notifications) => { - dispatch(updateNotifications({ notifications })); - dispatch(fetchingNotifications({ fetching: false })); - }) - .then(resolve) - .catch((err) => { - toast.error('An error occurred while fetching notifications'); - dispatch(fetchingNotifications({ fetching: false })); - console.error(err); - resolve(); - }); - }); -}; - export const completeQuitFlow = () => (dispatch, getState) => { const { ipcRenderer } = window; return ipcRenderer.invoke('main:complete-quit-flow'); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js new file mode 100644 index 000000000..60b4e2df7 --- /dev/null +++ b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js @@ -0,0 +1,106 @@ +import toast from 'react-hot-toast'; +import { createSlice } from '@reduxjs/toolkit'; +import { getAppInstallDate } from 'utils/common/platform'; + +const getReadNotificationIds = () => { + try { + let readNotificationIdsString = window.localStorage.getItem('bruno.notifications.read'); + let readNotificationIds = readNotificationIdsString ? JSON.parse(readNotificationIdsString) : []; + return readNotificationIds; + } catch (err) { + toast.error('An error occurred while fetching read notifications'); + } +}; + +const setReadNotificationsIds = (val) => { + try { + window.localStorage.setItem('bruno.notifications.read', JSON.stringify(val)); + } catch (err) { + toast.error('An error occurred while setting read notifications'); + } +}; + +const initialState = { + loading: false, + notifications: [], + readNotificationIds: getReadNotificationIds() || [] +}; + +export const notificationSlice = createSlice({ + name: 'notifications', + initialState, + reducers: { + setFetchingStatus: (state, action) => { + state.loading = action.payload.fetching; + }, + setNotifications: (state, action) => { + console.log('notifications', notifications); + let notifications = action.payload.notifications || []; + let readNotificationIds = state.readNotificationIds; + + // Ignore notifications sent before the app was installed + let appInstalledOnDate = getAppInstallDate(); + notifications = notifications.filter((notification) => { + const notificationDate = new Date(notification.date); + const appInstalledOn = new Date(appInstalledOnDate); + + notificationDate.setHours(0, 0, 0, 0); + appInstalledOn.setHours(0, 0, 0, 0); + + return notificationDate >= appInstalledOn; + }); + + state.notifications = notifications.map((notification) => { + return { + ...notification, + read: readNotificationIds.includes(notification.id) + }; + }); + }, + markNotificationAsRead: (state, action) => { + if (state.readNotificationIds.includes(action.payload.notificationId)) return; + + const notification = state.notifications.find( + (notification) => notification.id === action.payload.notificationId + ); + if (!notification) return; + + state.readNotificationIds.push(action.payload.notificationId); + setReadNotificationsIds(state.readNotificationIds); + notification.read = true; + }, + markAllNotificationsAsRead: (state) => { + let readNotificationIds = state.notifications.map((notification) => notification.id); + state.readNotificationIds = readNotificationIds; + setReadNotificationsIds(readNotificationIds); + + state.notifications.forEach((notification) => { + notification.read = true; + }); + } + } +}); + +export const { setNotifications, setFetchingStatus, markNotificationAsRead, markAllNotificationsAsRead } = + notificationSlice.actions; + +export const fetchNotifications = () => (dispatch, getState) => { + return new Promise((resolve) => { + const { ipcRenderer } = window; + dispatch(setFetchingStatus(true)); + ipcRenderer + .invoke('renderer:fetch-notifications') + .then((notifications) => { + dispatch(setNotifications({ notifications })); + dispatch(setFetchingStatus(false)); + resolve(notifications); + }) + .catch((err) => { + dispatch(setFetchingStatus(false)); + console.error(err); + resolve([]); + }); + }); +}; + +export default notificationSlice.reducer; diff --git a/packages/bruno-app/src/themes/dark.js b/packages/bruno-app/src/themes/dark.js index 26be4e822..bb1001f31 100644 --- a/packages/bruno-app/src/themes/dark.js +++ b/packages/bruno-app/src/themes/dark.js @@ -138,32 +138,16 @@ const darkTheme = { notifications: { bg: '#3D3D3D', - settings: { - bg: '#3D3D3D', - sidebar: { - bg: '#3D3D3D', - borderRight: '#4f4f4f' - }, - item: { + list: { + bg: '3D3D3D', + borderRight: '#4f4f4f', + borderBottom: '#545454', + hoverBg: '#434343', + active: { border: '#569cd6', - hoverBg: 'transparent', - borderBottom: '#4f4f4f99', - active: { - bg: '#4f4f4f', - hoverBg: '#4f4f4f' - }, - read: { - bg: '#4f4f4f55', - hoverBg: '#4f4f4f' - }, - date: { - color: '#ccc9' - } - }, - gridBorder: '#4f4f4f' - }, - bell: { - count: '#cc7b1b55' + bg: '#4f4f4f', + hoverBg: '#4f4f4f' + } } }, diff --git a/packages/bruno-app/src/themes/light.js b/packages/bruno-app/src/themes/light.js index f4c10b097..f359309ab 100644 --- a/packages/bruno-app/src/themes/light.js +++ b/packages/bruno-app/src/themes/light.js @@ -141,36 +141,17 @@ const lightTheme = { }, notifications: { - bg: '#efefef', - settings: { - bg: 'white', - sidebar: { - bg: '#eaeaea', - borderRight: 'transparent' - }, - item: { + bg: 'white', + list: { + bg: '#eaeaea', + borderRight: 'transparent', + borderBottom: '#d3d3d3', + hoverBg: '#e4e4e4', + active: { border: '#546de5', - borderBottom: '#4f4f4f44', - hoverBg: '#e4e4e4', - active: { - bg: '#dcdcdc', - hoverBg: '#dcdcdc' - }, - read: { - bg: '#dcdcdc55', - hoverBg: '#dcdcdc' - }, - date: { - color: '#5f5f5f' - } - }, - gridBorder: '#f4f4f4' - }, - sidebar: { - bg: '#eaeaea' - }, - bell: { - count: '#cc7b1b55' + bg: '#dcdcdc', + hoverBg: '#dcdcdc' + } } }, diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index a9471dfd7..f31dd228f 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -153,9 +153,6 @@ export const humanizeDate = (dateString) => { return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric' + day: 'numeric' }); }; diff --git a/packages/bruno-app/src/utils/common/notifications.js b/packages/bruno-app/src/utils/common/notifications.js deleted file mode 100644 index 602a90558..000000000 --- a/packages/bruno-app/src/utils/common/notifications.js +++ /dev/null @@ -1,19 +0,0 @@ -import toast from 'react-hot-toast'; - -export const getReadNotificationIds = () => { - try { - let readNotificationIdsString = window.localStorage.getItem('bruno.notifications.read'); - let readNotificationIds = readNotificationIdsString ? JSON.parse(readNotificationIdsString) : []; - return readNotificationIds; - } catch (err) { - toast.error('An error occurred while fetching read notifications'); - } -}; - -export const setReadNotificationsIds = (val) => { - try { - window.localStorage.setItem('bruno.notifications.read', JSON.stringify(val)); - } catch (err) { - toast.error('An error occurred while setting read notifications'); - } -}; diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 2ac2276cf..798c0db95 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -30,7 +30,13 @@ const template = [ } }, { type: 'separator' }, - { role: 'quit' } + { role: 'quit' }, + { + label: 'Force Quit', + click() { + process.exit(); + } + } ] }, {