mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
chore: remove disable notifications UI (#9814)
This commit is contained in:
parent
9d98d0771e
commit
da05c7be5c
@ -1,30 +0,0 @@
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
|
||||
const StyledBox = styled(Box)(() => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
minHeight: '150px',
|
||||
}));
|
||||
|
||||
const StyledNotificationsIcon = styled(NotificationsIcon)(({ theme }) => ({
|
||||
height: '30px',
|
||||
width: '30px',
|
||||
color: theme.palette.neutral.main,
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
interface IEmptyNotificationsProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const EmptyNotifications = ({ text }: IEmptyNotificationsProps) => {
|
||||
return (
|
||||
<StyledBox>
|
||||
<StyledNotificationsIcon />
|
||||
<Typography color='neutral.main'>{text}</Typography>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
@ -1,166 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
styled,
|
||||
Avatar,
|
||||
ListItemButton,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import type {
|
||||
NotificationsSchemaItem,
|
||||
NotificationsSchemaItemNotificationType,
|
||||
} from 'openapi';
|
||||
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
||||
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||
import ToggleOffOutlined from '@mui/icons-material/ToggleOffOutlined';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
|
||||
const StyledContainerBox = styled(Box, {
|
||||
shouldForwardProp: (prop) => prop !== 'readAt',
|
||||
})<{ readAt: boolean }>(({ theme, readAt }) => ({
|
||||
padding: theme.spacing(0.5),
|
||||
marginRight: theme.spacing(1.5),
|
||||
backgroundColor: readAt
|
||||
? theme.palette.neutral.light
|
||||
: theme.palette.secondary.light,
|
||||
width: '30px',
|
||||
height: '30px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
top: 3,
|
||||
left: 7,
|
||||
}));
|
||||
|
||||
const StyledListItemButton = styled(ListItemButton)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
padding: theme.spacing(2.5, 2, 3, 2),
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
width: '100%',
|
||||
'&:after': {
|
||||
content: '""',
|
||||
width: `calc(100% - ${theme.spacing(4)})`,
|
||||
height: '1px',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.divider,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledNotificationMessageBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const StyledSecondaryInfoBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
margin: theme.spacing(1, 0, 0, 5.25),
|
||||
}));
|
||||
|
||||
const StyledMessageTypography = styled(Typography, {
|
||||
shouldForwardProp: (prop) => prop !== 'readAt',
|
||||
})<{ readAt: boolean }>(({ theme, readAt }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
fontWeight: readAt ? 'normal' : 'bold',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
wordBreak: 'break-all',
|
||||
}));
|
||||
|
||||
const StyledTimeAgoTypography = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.neutral.main,
|
||||
}));
|
||||
|
||||
const StyledUserContainer = styled(Box)(({ theme }) => ({
|
||||
...flexRow,
|
||||
}));
|
||||
|
||||
const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
}));
|
||||
|
||||
const StyledCreatedBy = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.neutral.main,
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
|
||||
interface INotificationProps {
|
||||
notification: NotificationsSchemaItem;
|
||||
onNotificationClick: (notification: NotificationsSchemaItem) => void;
|
||||
}
|
||||
|
||||
export const Notification = ({
|
||||
notification,
|
||||
onNotificationClick,
|
||||
}: INotificationProps) => {
|
||||
const theme = useTheme();
|
||||
const { readAt } = notification;
|
||||
|
||||
const resolveIcon = (type: NotificationsSchemaItemNotificationType) => {
|
||||
if (type === 'change-request') {
|
||||
return (
|
||||
<StyledContainerBox readAt={Boolean(readAt)}>
|
||||
<ChangesAppliedIcon
|
||||
color={
|
||||
notification.readAt
|
||||
? theme.palette.neutral.main
|
||||
: theme.palette.primary.main
|
||||
}
|
||||
style={{ transform: 'scale(0.8)' }}
|
||||
/>
|
||||
</StyledContainerBox>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'toggle') {
|
||||
return (
|
||||
<StyledContainerBox readAt={Boolean(readAt)}>
|
||||
<ToggleOffOutlined
|
||||
sx={(theme) => ({
|
||||
height: '20px',
|
||||
width: '20px',
|
||||
color: readAt
|
||||
? theme.palette.neutral.main
|
||||
: theme.palette.primary.main,
|
||||
})}
|
||||
/>
|
||||
</StyledContainerBox>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledListItemButton onClick={() => onNotificationClick(notification)}>
|
||||
<StyledNotificationMessageBox>
|
||||
<StyledMessageTypography readAt={Boolean(readAt)}>
|
||||
{resolveIcon(notification.notificationType)}{' '}
|
||||
{notification.message}
|
||||
</StyledMessageTypography>
|
||||
<StyledSecondaryInfoBox>
|
||||
<StyledUserContainer>
|
||||
<StyledAvatar
|
||||
src={notification.createdBy.imageUrl || ''}
|
||||
/>
|
||||
<StyledCreatedBy>
|
||||
Created by {notification.createdBy.username}
|
||||
</StyledCreatedBy>
|
||||
</StyledUserContainer>
|
||||
|
||||
<StyledTimeAgoTypography>
|
||||
<TimeAgo date={notification.createdAt} />
|
||||
</StyledTimeAgoTypography>
|
||||
</StyledSecondaryInfoBox>
|
||||
</StyledNotificationMessageBox>
|
||||
</StyledListItemButton>
|
||||
);
|
||||
};
|
@ -1,259 +0,0 @@
|
||||
import { type KeyboardEvent, useState } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
Typography,
|
||||
Box,
|
||||
IconButton,
|
||||
styled,
|
||||
ClickAwayListener,
|
||||
Button,
|
||||
Switch,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { useNotifications } from 'hooks/api/getters/useNotifications/useNotifications';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
import { NotificationsHeader } from './NotificationsHeader';
|
||||
import { NotificationsList } from './NotificationsList';
|
||||
import { Notification } from './Notification';
|
||||
import { EmptyNotifications } from './EmptyNotifications';
|
||||
import type { NotificationsSchemaItem } from 'openapi';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNotificationsApi } from 'hooks/api/actions/useNotificationsApi/useNotificationsApi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { Feedback } from 'component/common/Feedback/Feedback';
|
||||
|
||||
const StyledPrimaryContainerBox = styled(Box)(() => ({
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
const StyledInnerContainerBox = styled(Box)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.elevation1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.primary.main,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
width: '420px',
|
||||
boxShadow: theme.boxShadows.popup,
|
||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||
position: 'absolute',
|
||||
right: -100,
|
||||
top: 60,
|
||||
maxHeight: '80vh',
|
||||
overflowY: 'auto',
|
||||
}));
|
||||
|
||||
const StyledDotBox = styled(Box)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: '100%',
|
||||
width: '7px',
|
||||
height: '7px',
|
||||
position: 'absolute',
|
||||
top: 11,
|
||||
right: 11,
|
||||
}));
|
||||
|
||||
const StyledHeaderBox = styled(Box)(() => ({
|
||||
...flexRow,
|
||||
}));
|
||||
|
||||
const StyledHeaderTypography = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||
'&:focus-visible': {
|
||||
outlineStyle: 'solid',
|
||||
outlineWidth: 2,
|
||||
outlineColor: theme.palette.primary.main,
|
||||
borderRadius: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Notifications = () => {
|
||||
const [showNotifications, setShowNotifications] = useState(false);
|
||||
const { notifications, refetchNotifications } = useNotifications({
|
||||
refreshInterval: 15000,
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const { markAsRead } = useNotificationsApi();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const [showUnread, setShowUnread] = useState(false);
|
||||
|
||||
const onNotificationClick = async (
|
||||
notification: NotificationsSchemaItem,
|
||||
) => {
|
||||
if (notification.link) {
|
||||
navigate(notification.link);
|
||||
}
|
||||
|
||||
if (uiConfig?.flags?.T) {
|
||||
trackEvent('notifications', {
|
||||
props: { eventType: notification.notificationType },
|
||||
});
|
||||
}
|
||||
|
||||
setShowNotifications(false);
|
||||
|
||||
try {
|
||||
await markAsRead({
|
||||
notifications: [notification.id],
|
||||
});
|
||||
refetchNotifications();
|
||||
} catch (e) {
|
||||
// No need to display this in the UI. Minor inconvinence if this call fails.
|
||||
console.error('Error marking notification as read: ', e);
|
||||
}
|
||||
};
|
||||
|
||||
const onMarkAllAsRead = async () => {
|
||||
try {
|
||||
if (notifications && notifications.length > 0) {
|
||||
await markAsRead({
|
||||
notifications: notifications.map(
|
||||
(notification) => notification.id,
|
||||
),
|
||||
});
|
||||
refetchNotifications();
|
||||
}
|
||||
} catch (e) {
|
||||
// No need to display this in the UI. Minor inconvinence if this call fails.
|
||||
console.error('Error marking all notification as read: ', e);
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
setShowNotifications(false);
|
||||
}
|
||||
};
|
||||
|
||||
const unreadNotifications = notifications?.filter(
|
||||
(notification) => notification.readAt === null,
|
||||
);
|
||||
|
||||
const hasUnreadNotifications = Boolean(
|
||||
unreadNotifications && unreadNotifications.length > 0,
|
||||
);
|
||||
|
||||
const filterUnread = (notification: NotificationsSchemaItem) => {
|
||||
if (showUnread) {
|
||||
return !notification.readAt;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const notificationComponents = notifications
|
||||
?.filter(filterUnread)
|
||||
.map((notification) => (
|
||||
<Notification
|
||||
notification={notification}
|
||||
key={notification.id}
|
||||
onNotificationClick={onNotificationClick}
|
||||
/>
|
||||
));
|
||||
|
||||
const shouldShowFeedback = Boolean(
|
||||
notifications && notifications.length > 0 && !showUnread,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledPrimaryContainerBox>
|
||||
<Tooltip title='Notifications' arrow>
|
||||
<StyledIconButton
|
||||
onClick={() => setShowNotifications(!showNotifications)}
|
||||
data-testid='NOTIFICATIONS_BUTTON'
|
||||
size='large'
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={hasUnreadNotifications}
|
||||
show={<StyledDotBox />}
|
||||
/>
|
||||
<NotificationsIcon />
|
||||
</StyledIconButton>
|
||||
</Tooltip>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={showNotifications}
|
||||
show={
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setShowNotifications(false)}
|
||||
>
|
||||
<StyledPaper
|
||||
className='dropdown-outline'
|
||||
onKeyDown={onKeyDown}
|
||||
data-testid='NOTIFICATIONS_MODAL'
|
||||
>
|
||||
<NotificationsHeader>
|
||||
<StyledHeaderBox>
|
||||
<StyledHeaderTypography>
|
||||
Show only unread
|
||||
</StyledHeaderTypography>
|
||||
<Switch
|
||||
onClick={() =>
|
||||
setShowUnread(!showUnread)
|
||||
}
|
||||
checked={showUnread}
|
||||
/>
|
||||
</StyledHeaderBox>
|
||||
</NotificationsHeader>
|
||||
<ConditionallyRender
|
||||
condition={hasUnreadNotifications}
|
||||
show={
|
||||
<StyledInnerContainerBox data-testid='UNREAD_NOTIFICATIONS'>
|
||||
<Button onClick={onMarkAllAsRead}>
|
||||
<StyledTypography>
|
||||
Mark all as read (
|
||||
{unreadNotifications?.length})
|
||||
</StyledTypography>
|
||||
</Button>
|
||||
</StyledInnerContainerBox>
|
||||
}
|
||||
/>{' '}
|
||||
<ConditionallyRender
|
||||
condition={notificationComponents?.length === 0}
|
||||
show={
|
||||
<EmptyNotifications
|
||||
text={
|
||||
showUnread
|
||||
? 'No unread notifications'
|
||||
: 'No new notifications'
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<NotificationsList>
|
||||
{notificationComponents}
|
||||
</NotificationsList>
|
||||
<ConditionallyRender
|
||||
condition={shouldShowFeedback}
|
||||
show={
|
||||
<>
|
||||
<Feedback
|
||||
eventName='notifications'
|
||||
id='useful'
|
||||
localStorageKey='NotificationsUsefulPrompt'
|
||||
/>
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledPaper>
|
||||
</ClickAwayListener>
|
||||
}
|
||||
/>
|
||||
</StyledPrimaryContainerBox>
|
||||
);
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
import { Typography, styled, Box } from '@mui/material';
|
||||
import type React from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const StyledOuterContainerBox = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 1.5, 1, 3),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
boxShadow: theme.boxShadows.separator,
|
||||
}));
|
||||
|
||||
export const NotificationsHeader: FC<{ children?: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<StyledOuterContainerBox>
|
||||
<Typography fontWeight='bold'>Notifications</Typography>
|
||||
{children}
|
||||
</StyledOuterContainerBox>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
import { List, styled } from '@mui/material';
|
||||
import type React from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const StyledListContainer = styled(List)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 1, 3, 1),
|
||||
}));
|
||||
|
||||
export const NotificationsList: FC<{ children?: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<StyledListContainer data-testid='NOTIFICATIONS_LIST'>
|
||||
{children}
|
||||
</StyledListContainer>
|
||||
);
|
||||
};
|
@ -15,13 +15,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||
|
||||
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined';
|
||||
import LightModeOutlined from '@mui/icons-material/LightModeOutlined';
|
||||
import { useThemeMode } from 'hooks/useThemeMode';
|
||||
import { Notifications } from 'component/common/Notifications/Notifications';
|
||||
import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { CommandBar } from 'component/commandBar/CommandBar';
|
||||
|
||||
const HeaderComponent = styled(AppBar)(({ theme }) => ({
|
||||
@ -81,8 +78,6 @@ const Header = () => {
|
||||
const { onSetThemeMode, themeMode } = useThemeMode();
|
||||
const theme = useTheme();
|
||||
|
||||
const disableNotifications = useUiFlag('disableNotifications');
|
||||
const { isOss } = useUiConfig();
|
||||
const smallScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const toggleDrawer = () => setOpenDrawer((prev) => !prev);
|
||||
@ -148,10 +143,6 @@ const Header = () => {
|
||||
/>
|
||||
</StyledIconButton>
|
||||
</Tooltip>
|
||||
<ConditionallyRender
|
||||
condition={!isOss() && !disableNotifications}
|
||||
show={<Notifications />}
|
||||
/>
|
||||
<Tooltip title='Documentation' arrow>
|
||||
<StyledIconButton
|
||||
component='a'
|
||||
|
@ -63,7 +63,6 @@ export type UiFlags = {
|
||||
demo?: boolean;
|
||||
googleAuthEnabled?: boolean;
|
||||
disableBulkToggle?: boolean;
|
||||
disableNotifications?: boolean;
|
||||
advancedPlayground?: boolean;
|
||||
strategyVariant?: boolean;
|
||||
doraMetrics?: boolean;
|
||||
|
@ -20,7 +20,6 @@ export type IFlagKey =
|
||||
| 'demo'
|
||||
| 'googleAuthEnabled'
|
||||
| 'disableBulkToggle'
|
||||
| 'disableNotifications'
|
||||
| 'advancedPlayground'
|
||||
| 'filterInvalidClientMetrics'
|
||||
| 'filterExistingFlagNames'
|
||||
@ -124,10 +123,6 @@ const flags: IFlags = {
|
||||
process.env.DISABLE_BULK_TOGGLE,
|
||||
false,
|
||||
),
|
||||
disableNotifications: parseEnvVarBoolean(
|
||||
process.env.DISABLE_NOTIFICATIONS,
|
||||
false,
|
||||
),
|
||||
filterInvalidClientMetrics: parseEnvVarBoolean(
|
||||
process.env.FILTER_INVALID_CLIENT_METRICS,
|
||||
false,
|
||||
|
Loading…
Reference in New Issue
Block a user