mirror of
https://github.com/usebruno/bruno.git
synced 2025-08-17 22:49:54 +02:00
feat: refactor and improve notifications implementation
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
ENV=production
|
||||
|
||||
NEXT_PUBLIC_ENV=prod
|
||||
|
||||
NEXT_PUBLIC_BRUNO_SERVER_API=https://ada.grafnode.com/api
|
||||
NEXT_PUBLIC_ENV=prod
|
@@ -1,13 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ModalHeader = ({ title, handleCancel, headerContentComponent }) => (
|
||||
const ModalHeader = ({ title, handleCancel, customHeader }) => (
|
||||
<div className="bruno-modal-header">
|
||||
{headerContentComponent ? (
|
||||
headerContentComponent
|
||||
) : (
|
||||
<>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>
|
||||
)}
|
||||
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
||||
{handleCancel ? (
|
||||
<div className="close cursor-pointer" onClick={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 (
|
||||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<ModalHeader
|
||||
title={title}
|
||||
handleCancel={() => closeModal({ type: 'icon' })}
|
||||
headerContentComponent={headerContentComponent}
|
||||
/>
|
||||
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} customHeader={customHeader} />
|
||||
<ModalContent>{children}</ModalContent>
|
||||
<ModalFooter
|
||||
confirmText={confirmText}
|
||||
|
@@ -4,73 +4,67 @@ const StyledWrapper = styled.div`
|
||||
.notifications-modal {
|
||||
margin-inline: -1rem;
|
||||
margin-block: -1.5rem;
|
||||
background-color: ${(props) => 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};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -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 = (
|
||||
<div className="flex flex-row gap-8">
|
||||
<div>NOTIFICATIONS</div>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<>
|
||||
<div className="normal-case font-normal">
|
||||
{unreadNotifications.length} <i>unread notifications</i>
|
||||
{unreadNotifications.length} <span>unread notifications</span>
|
||||
</div>
|
||||
<button
|
||||
className={`select-none ${1 == 2 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'}`}
|
||||
onClick={() => {
|
||||
let allNotificationIds = notifications.map((notification) => notification.id);
|
||||
dispatch(markMultipleNotificationsAsRead({ notificationIds: allNotificationIds }));
|
||||
}}
|
||||
className={`select-none ${1 == 2 ? 'opacity-50' : 'text-link mark-as-read cursor-pointer hover:underline'}`}
|
||||
onClick={() => dispatch(markAllNotificationsAsRead())}
|
||||
>
|
||||
{'Mark all as read'}
|
||||
</button>
|
||||
@@ -92,7 +88,7 @@ const Notifications = () => {
|
||||
className="relative"
|
||||
onClick={() => {
|
||||
dispatch(fetchNotifications());
|
||||
toggleNotificationsModal(true);
|
||||
setShowNotificationsModal(true);
|
||||
}}
|
||||
>
|
||||
<IconBell
|
||||
@@ -104,51 +100,52 @@ const Notifications = () => {
|
||||
<div className="notification-count text-xs">{unreadNotifications.length}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showNotificationsModal && (
|
||||
<Modal
|
||||
size="lg"
|
||||
title="Notifications"
|
||||
confirmText={'Close'}
|
||||
handleConfirm={() => {
|
||||
toggleNotificationsModal(false);
|
||||
setShowNotificationsModal(false);
|
||||
}}
|
||||
handleCancel={() => {
|
||||
toggleNotificationsModal(false);
|
||||
setShowNotificationsModal(false);
|
||||
}}
|
||||
hideFooter={true}
|
||||
headerContentComponent={modalHeaderContentComponent}
|
||||
customHeader={modalCustomHeader}
|
||||
disableCloseOnOutsideClick={true}
|
||||
disableEscapeKey={true}
|
||||
>
|
||||
<div className="notifications-modal">
|
||||
{notifications?.length > 0 ? (
|
||||
<div className="grid grid-cols-4 flex flex-row text-sm">
|
||||
<div className="col-span-1 flex flex-col">
|
||||
<ul
|
||||
className="w-full flex flex-col h-[50vh] max-h-[50vh] overflow-y-auto"
|
||||
className="notifications w-full flex flex-col h-[50vh] max-h-[50vh] overflow-y-auto"
|
||||
style={{ maxHeight: '50vh', height: '46vh' }}
|
||||
>
|
||||
{notifications?.slice(notificationsStartIndex, notificationsEndIndex)?.map((notification) => (
|
||||
<li
|
||||
className={`p-4 flex flex-col gap-2 ${
|
||||
selectedNotification?.id == notification?.id ? 'active' : notification?.read ? 'read' : ''
|
||||
key={notification.id}
|
||||
className={`p-4 flex flex-col justify-center ${
|
||||
selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
|
||||
}`}
|
||||
onClick={handleNotificationItemClick(notification)}
|
||||
>
|
||||
<div className="notification-title w-full">{notification?.title}</div>
|
||||
{/* human readable relative date */}
|
||||
<div className="notification-date w-full flex justify-start font-normal text-xs py-2">
|
||||
{relativeDate(notification?.date)}
|
||||
</div>
|
||||
<div className="notification-date text-xs py-2">{relativeDate(notification?.date)}</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="w-full pagination flex flex-row gap-4 justify-center py-4 items-center">
|
||||
<div className="w-full pagination flex flex-row gap-4 justify-center p-2 items-center text-xs">
|
||||
<button
|
||||
className={`pl-2 pr-2 py-3 select-none ${
|
||||
pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
|
||||
}`}
|
||||
onClick={handlePrev}
|
||||
>
|
||||
{'Previous'}
|
||||
{'Prev'}
|
||||
</button>
|
||||
<div className="flex flex-row items-center justify-center gap-1">
|
||||
Page
|
||||
@@ -170,9 +167,11 @@ const Notifications = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full col-span-3 p-4 flex-col gap-2">
|
||||
<div className="w-full text-lg flex flex-wrap h-fit">{selectedNotification?.title}</div>
|
||||
<div className="w-full notification-date">{humanizeDate(selectedNotification?.date)}</div>
|
||||
<div className="flex w-full col-span-3 p-4 flex-col">
|
||||
<div className="w-full text-lg flex flex-wrap h-fit mb-1">{selectedNotification?.title}</div>
|
||||
<div className="w-full notification-date text-xs mb-4">
|
||||
{humanizeDate(selectedNotification?.date)}
|
||||
</div>
|
||||
<div
|
||||
className="flex w-full flex-col flex-wrap h-fit"
|
||||
dangerouslySetInnerHTML={{ __html: selectedNotification?.description }}
|
||||
|
@@ -11,7 +11,7 @@ import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IconSettings, IconCookie, IconHeart } from '@tabler/icons';
|
||||
import { updateLeftSidebarWidth, updateIsDragging, showPreferences } from 'providers/ReduxStore/slices/app';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import Notifications from 'components/Notifications/index';
|
||||
import Notifications from 'components/Notifications';
|
||||
|
||||
const MIN_LEFT_SIDEBAR_WIDTH = 221;
|
||||
const MAX_LEFT_SIDEBAR_WIDTH = 600;
|
||||
|
@@ -141,18 +141,6 @@ const GlobalStyle = createGlobalStyle`
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-and-pulse {
|
||||
0% {
|
||||
scale: 1;
|
||||
}
|
||||
20% {
|
||||
scale: 1.5;
|
||||
}
|
||||
100% {
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotateClockwise {
|
||||
0% {
|
||||
transform: scaleY(-1) rotate(0deg);
|
||||
|
@@ -5,6 +5,7 @@ import debugMiddleware from './middlewares/debug/middleware';
|
||||
import appReducer from './slices/app';
|
||||
import collectionsReducer from './slices/collections';
|
||||
import tabsReducer from './slices/tabs';
|
||||
import notificationsReducer from './slices/notifications';
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const isDevEnv = () => {
|
||||
@@ -20,7 +21,8 @@ export const store = configureStore({
|
||||
reducer: {
|
||||
app: appReducer,
|
||||
collections: collectionsReducer,
|
||||
tabs: tabsReducer
|
||||
tabs: tabsReducer,
|
||||
notifications: notificationsReducer
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
||||
});
|
||||
|
@@ -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');
|
||||
|
@@ -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;
|
@@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -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'
|
||||
});
|
||||
};
|
||||
|
@@ -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');
|
||||
}
|
||||
};
|
@@ -30,7 +30,13 @@ const template = [
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
{ role: 'quit' },
|
||||
{
|
||||
label: 'Force Quit',
|
||||
click() {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user