Skip to content

Commit

Permalink
feat: refactor and improve notifications implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
helloanoop committed Mar 11, 2024
1 parent b0f4491 commit 6a2754d
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 391 deletions.
183 changes: 79 additions & 104 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions packages/bruno-app/.env.production
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
ENV=production

NEXT_PUBLIC_ENV=prod

NEXT_PUBLIC_BRUNO_SERVER_API=https://ada.grafnode.com/api
NEXT_PUBLIC_ENV=prod
16 changes: 4 additions & 12 deletions packages/bruno-app/src/components/Modal/index.js
Original file line number Diff line number Diff line change
@@ -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}>
×
Expand Down Expand Up @@ -58,7 +54,7 @@ const ModalFooter = ({
const Modal = ({
size,
title,
headerContentComponent,
customHeader,
confirmText,
cancelText,
handleCancel,
Expand Down Expand Up @@ -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}
Expand Down
92 changes: 43 additions & 49 deletions packages/bruno-app/src/components/Notifications/StyleWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
display: flex;
color: white;
position: absolute;
right: -10px;
top: -15px;
z-index: 10;
top: -0.625rem;
right: -0.5rem;
margin-right: 0.5rem;
background-color: ${(props) => props.theme.notifications.bell.count};
border-radius: 50%;
padding: 2px 1px;
min-width: 20px;
display: flex;
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 {
color: ${(props) => props.theme.text} !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;
.notification-date {
font-size: 0.6875rem;
}
}
}
.notification-title {
// text ellipses 2 lines
// white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
Expand All @@ -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};
}
`;

Expand Down
69 changes: 34 additions & 35 deletions packages/bruno-app/src/components/Notifications/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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>
Expand All @@ -92,7 +88,7 @@ const Notifications = () => {
className="relative"
onClick={() => {
dispatch(fetchNotifications());
toggleNotificationsModal(true);
setShowNotificationsModal(true);
}}
>
<IconBell
Expand All @@ -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
Expand All @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-app/src/components/Sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 0 additions & 12 deletions packages/bruno-app/src/globalStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion packages/bruno-app/src/providers/ReduxStore/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -20,7 +21,8 @@ export const store = configureStore({
reducer: {
app: appReducer,
collections: collectionsReducer,
tabs: tabsReducer
tabs: tabsReducer,
notifications: notificationsReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
});
Expand Down
Loading

0 comments on commit 6a2754d

Please sign in to comment.