mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-23 01:16:27 +02:00
Feat/notifications UI followup (#3197)
This PR adds more capabilities to the notification UI. Including: * Displaying new notification types * Update visual expression based on whether it's read or not * Mark items as read * Follow the items link to go to the notification destination * Cleanup and styled components
This commit is contained in:
parent
e9892a4ec9
commit
ef2f184845
@ -0,0 +1,26 @@
|
|||||||
|
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),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const EmptyNotifications = () => {
|
||||||
|
return (
|
||||||
|
<StyledBox>
|
||||||
|
<StyledNotificationsIcon />
|
||||||
|
<Typography color="neutral.main">No new notifications</Typography>
|
||||||
|
</StyledBox>
|
||||||
|
);
|
||||||
|
};
|
126
frontend/src/component/common/Notifications/Notification.tsx
Normal file
126
frontend/src/component/common/Notifications/Notification.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { useTheme } from '@mui/material';
|
||||||
|
import { Box, ListItem, Typography, styled } from '@mui/material';
|
||||||
|
import {
|
||||||
|
NotificationsSchemaItem,
|
||||||
|
NotificationsSchemaItemNotificationType,
|
||||||
|
} from 'openapi';
|
||||||
|
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
||||||
|
import TimeAgo from 'react-timeago';
|
||||||
|
import { ToggleOffOutlined } from '@mui/icons-material';
|
||||||
|
|
||||||
|
const StyledContainerBox = styled(Box, {
|
||||||
|
shouldForwardProp: prop => prop !== 'readAt',
|
||||||
|
})<{ readAt: boolean }>(({ theme, readAt }) => ({
|
||||||
|
padding: theme.spacing(0.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`,
|
||||||
|
position: 'absolute',
|
||||||
|
top: 3,
|
||||||
|
left: 7,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledListItem = styled(ListItem)(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
cursor: 'pointer',
|
||||||
|
margin: theme.spacing(2, 0),
|
||||||
|
'&:not(:last-child)': {
|
||||||
|
borderBottom: `2px solid ${theme.palette.tertiary.contrast}`,
|
||||||
|
},
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledNotificationMessageBox = styled(Box)(({ theme }) => ({
|
||||||
|
marginLeft: theme.spacing(4),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSecondaryInfoBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
margin: theme.spacing(1, 0, 1, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMessageTypography = styled(Typography, {
|
||||||
|
shouldForwardProp: prop => prop !== 'readAt',
|
||||||
|
})<{ readAt: boolean }>(({ theme, readAt }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
fontWeight: readAt ? 'normal' : 'bold',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTimeAgoTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
color: theme.palette.neutral.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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: Boolean(readAt)
|
||||||
|
? theme.palette.neutral.main
|
||||||
|
: theme.palette.primary.main,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</StyledContainerBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledListItem onClick={() => onNotificationClick(notification)}>
|
||||||
|
{resolveIcon(notification.notificationType)}{' '}
|
||||||
|
<StyledNotificationMessageBox>
|
||||||
|
<StyledMessageTypography readAt={Boolean(readAt)}>
|
||||||
|
{notification.message}
|
||||||
|
</StyledMessageTypography>
|
||||||
|
<StyledSecondaryInfoBox>
|
||||||
|
<StyledTimeAgoTypography>
|
||||||
|
<TimeAgo date={new Date(notification.createdAt)} />
|
||||||
|
</StyledTimeAgoTypography>
|
||||||
|
</StyledSecondaryInfoBox>
|
||||||
|
</StyledNotificationMessageBox>
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
};
|
@ -1,61 +1,163 @@
|
|||||||
import Settings from '@mui/icons-material/Settings';
|
import { useState } from 'react';
|
||||||
import { Paper, Typography, Box, IconButton } from '@mui/material';
|
import {
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
styled,
|
||||||
|
ClickAwayListener,
|
||||||
|
Button,
|
||||||
|
} from '@mui/material';
|
||||||
import { useNotifications } from 'hooks/api/getters/useNotifications/useNotifications';
|
import { useNotifications } from 'hooks/api/getters/useNotifications/useNotifications';
|
||||||
|
import { ConditionallyRender } from '../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 { NotificationsSchemaItem } from 'openapi';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useNotificationsApi } from 'hooks/api/actions/useNotificationsApi/useNotificationsApi';
|
||||||
|
|
||||||
|
const StyledPrimaryContainerBox = styled(Box)(() => ({
|
||||||
|
position: 'relative',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInnerContainerBox = styled(Box)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.neutral.light,
|
||||||
|
padding: theme.spacing(1, 3),
|
||||||
|
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 }) => ({
|
||||||
|
minWidth: '400px',
|
||||||
|
boxShadow: theme.boxShadows.popup,
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
position: 'absolute',
|
||||||
|
right: -20,
|
||||||
|
top: 60,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDotBox = styled(Box)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: '100%',
|
||||||
|
width: '7px',
|
||||||
|
height: '7px',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 7,
|
||||||
|
right: 4,
|
||||||
|
}));
|
||||||
|
|
||||||
export const Notifications = () => {
|
export const Notifications = () => {
|
||||||
const { notifications } = useNotifications({ refreshInterval: 15 });
|
const [showNotifications, setShowNotifications] = useState(false);
|
||||||
|
const { notifications, refetchNotifications } = useNotifications({
|
||||||
|
refreshInterval: 15,
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { markAsRead } = useNotificationsApi();
|
||||||
|
|
||||||
|
const onNotificationClick = (notification: NotificationsSchemaItem) => {
|
||||||
|
if (notification.link) {
|
||||||
|
navigate(notification.link);
|
||||||
|
}
|
||||||
|
setShowNotifications(false);
|
||||||
|
|
||||||
|
// Intentionally not wait for this request. We don't want to hold the user back
|
||||||
|
// only to mark a notification as read.
|
||||||
|
try {
|
||||||
|
markAsRead({
|
||||||
|
notifications: [notification.id],
|
||||||
|
});
|
||||||
|
} 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 = () => {
|
||||||
|
try {
|
||||||
|
if (notifications && notifications.length > 0) {
|
||||||
|
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 unreadNotifications = notifications?.filter(
|
||||||
|
notification => notification.readAt === null
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasUnreadNotifications = Boolean(
|
||||||
|
unreadNotifications && unreadNotifications.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
console.log(notifications);
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<StyledPrimaryContainerBox>
|
||||||
elevation={0}
|
<IconButton
|
||||||
sx={theme => ({
|
onClick={() => setShowNotifications(!showNotifications)}
|
||||||
minWidth: '400px',
|
|
||||||
boxShadow: theme.boxShadows.popup,
|
|
||||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
|
||||||
position: 'absolute',
|
|
||||||
right: -20,
|
|
||||||
top: 60,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={theme => ({
|
|
||||||
padding: theme.spacing(1, 3),
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<Typography fontWeight="bold">Notifications</Typography>
|
<ConditionallyRender
|
||||||
|
condition={hasUnreadNotifications}
|
||||||
|
show={<StyledDotBox />}
|
||||||
|
/>
|
||||||
|
<NotificationsIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<ConditionallyRender
|
||||||
<IconButton>
|
condition={showNotifications}
|
||||||
<Settings />
|
show={
|
||||||
</IconButton>
|
<ClickAwayListener
|
||||||
</Box>
|
onClickAway={() => setShowNotifications(false)}
|
||||||
</Box>
|
>
|
||||||
|
<StyledPaper>
|
||||||
<Box
|
<NotificationsHeader />
|
||||||
sx={theme => ({
|
<ConditionallyRender
|
||||||
backgroundColor: theme.palette.neutral.light,
|
condition={hasUnreadNotifications}
|
||||||
padding: theme.spacing(1, 3),
|
show={
|
||||||
})}
|
<StyledInnerContainerBox>
|
||||||
>
|
<Button onClick={onMarkAllAsRead}>
|
||||||
<Typography
|
<StyledTypography>
|
||||||
sx={theme => ({
|
Mark all as read (
|
||||||
fontWeight: 'bold',
|
{unreadNotifications?.length})
|
||||||
fontSize: theme.fontSizes.smallBody,
|
</StyledTypography>
|
||||||
color: theme.palette.primary.main,
|
</Button>
|
||||||
textAlign: 'center',
|
</StyledInnerContainerBox>
|
||||||
})}
|
}
|
||||||
>
|
/>{' '}
|
||||||
Mark all as read ({notifications?.length})
|
<ConditionallyRender
|
||||||
</Typography>
|
condition={notifications?.length === 0}
|
||||||
</Box>
|
show={<EmptyNotifications />}
|
||||||
|
/>
|
||||||
<Box sx={theme => ({ padding: theme.spacing(2, 3) })}>
|
<NotificationsList>
|
||||||
List goes here
|
{notifications?.map(notification => (
|
||||||
</Box>
|
<Notification
|
||||||
</Paper>
|
notification={notification}
|
||||||
|
key={notification.id}
|
||||||
|
onNotificationClick={
|
||||||
|
onNotificationClick
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</NotificationsList>
|
||||||
|
</StyledPaper>
|
||||||
|
</ClickAwayListener>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledPrimaryContainerBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import Settings from '@mui/icons-material/Settings';
|
||||||
|
import { Typography, Box, IconButton, styled } from '@mui/material';
|
||||||
|
import { flexRow } from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledOuterContainerBox = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(1, 3),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSettingsContainer = styled(Box)(() => ({
|
||||||
|
...flexRow,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const NotificationsHeader = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledOuterContainerBox>
|
||||||
|
<Typography fontWeight="bold">Notifications</Typography>
|
||||||
|
|
||||||
|
<StyledSettingsContainer>
|
||||||
|
<IconButton>
|
||||||
|
<Settings />
|
||||||
|
</IconButton>
|
||||||
|
</StyledSettingsContainer>
|
||||||
|
</StyledOuterContainerBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
import { List, styled } from '@mui/material';
|
||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
|
const StyledListContainer = styled(List)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const NotificationsList: FC = ({ children }) => {
|
||||||
|
return <StyledListContainer>{children}</StyledListContainer>;
|
||||||
|
};
|
@ -20,7 +20,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||||
import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg';
|
import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg';
|
||||||
import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText.svg';
|
import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText.svg';
|
||||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
|
||||||
|
|
||||||
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
import { DrawerMenu } from './DrawerMenu/DrawerMenu';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
@ -33,7 +32,7 @@ import {
|
|||||||
adminMenuRoutes,
|
adminMenuRoutes,
|
||||||
getCondensedRoutes,
|
getCondensedRoutes,
|
||||||
} from 'component/menu/routes';
|
} from 'component/menu/routes';
|
||||||
import { KeyboardArrowDown, NotificationAdd } from '@mui/icons-material';
|
import { KeyboardArrowDown } from '@mui/icons-material';
|
||||||
import { filterByConfig } from 'component/common/util';
|
import { filterByConfig } from 'component/common/util';
|
||||||
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
|
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
|
||||||
import { useId } from 'hooks/useId';
|
import { useId } from 'hooks/useId';
|
||||||
@ -118,7 +117,6 @@ const Header: VFC = () => {
|
|||||||
const configId = useId();
|
const configId = useId();
|
||||||
const [adminRef, setAdminRef] = useState<HTMLButtonElement | null>(null);
|
const [adminRef, setAdminRef] = useState<HTMLButtonElement | null>(null);
|
||||||
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(null);
|
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(null);
|
||||||
const [showNotifications, setShowNotifications] = useState(false);
|
|
||||||
|
|
||||||
const [admin, setAdmin] = useState(false);
|
const [admin, setAdmin] = useState(false);
|
||||||
const { permissions } = useAuthPermissions();
|
const { permissions } = useAuthPermissions();
|
||||||
@ -252,6 +250,10 @@ const Header: VFC = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(uiConfig?.flags?.notifications)}
|
||||||
|
show={<Notifications />}
|
||||||
|
/>
|
||||||
<Tooltip title="Documentation" arrow>
|
<Tooltip title="Documentation" arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
href="https://docs.getunleash.io/"
|
href="https://docs.getunleash.io/"
|
||||||
@ -287,27 +289,6 @@ const Header: VFC = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(uiConfig?.flags?.notifications)}
|
|
||||||
show={
|
|
||||||
<Box sx={{ position: 'relative' }}>
|
|
||||||
<StyledIconButton
|
|
||||||
onClick={() =>
|
|
||||||
setShowNotifications(
|
|
||||||
!showNotifications
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<NotificationsIcon />
|
|
||||||
</StyledIconButton>
|
|
||||||
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={showNotifications}
|
|
||||||
show={<Notifications />}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<NavigationMenu
|
<NavigationMenu
|
||||||
id={adminId}
|
id={adminId}
|
||||||
options={filteredMainRoutes.adminRoutes}
|
options={filteredMainRoutes.adminRoutes}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { NotificationsSchemaItem } from 'openapi';
|
||||||
|
import useAPI from '../useApi/useApi';
|
||||||
|
|
||||||
|
export const useNotificationsApi = () => {
|
||||||
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
|
propagateErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const markAsRead = async (payload: { notifications: number[] }) => {
|
||||||
|
const path = `api/admin/notifications/read`;
|
||||||
|
const req = createRequest(path, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await makeRequest(req.caller, req.id);
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
errors,
|
||||||
|
markAsRead,
|
||||||
|
};
|
||||||
|
};
|
@ -16,7 +16,6 @@ export const useNotifications = (options: SWRConfiguration = {}) => {
|
|||||||
mutate().catch(console.warn);
|
mutate().catch(console.warn);
|
||||||
}, [mutate]);
|
}, [mutate]);
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
return {
|
return {
|
||||||
notifications: data,
|
notifications: data,
|
||||||
error,
|
error,
|
||||||
|
@ -5,10 +5,19 @@
|
|||||||
* OpenAPI spec version: 4.22.0-beta.3
|
* OpenAPI spec version: 4.22.0-beta.3
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data required to create a new [environment](https://docs.getunleash.io/reference/environments)
|
||||||
|
*/
|
||||||
export interface CreateEnvironmentSchema {
|
export interface CreateEnvironmentSchema {
|
||||||
/** The name of the environment. Must be a URL-friendly string according to [RFC 3968, section 2.3](https://www.rfc-editor.org/rfc/rfc3986#section-2.3) */
|
/** The name of the environment. Must be a URL-friendly string according to [RFC 3968, section 2.3](https://www.rfc-editor.org/rfc/rfc3986#section-2.3) */
|
||||||
name: string;
|
name: string;
|
||||||
/** The type of environment you would like to create (i.e. development or production). */
|
/** The [type of environment](https://docs.getunleash.io/reference/environments#environment-types) you would like to create. Unleash officially recognizes the following values:
|
||||||
|
- `development`
|
||||||
|
- `test`
|
||||||
|
- `preproduction`
|
||||||
|
- `production`
|
||||||
|
|
||||||
|
If you pass a string that is not one of the recognized values, Unleash will accept it, but it will carry no special semantics. */
|
||||||
type: string;
|
type: string;
|
||||||
/** Newly created environments are enabled by default. Set this property to `false` to create the environment in a disabled state. */
|
/** Newly created environments are enabled by default. Set this property to `false` to create the environment in a disabled state. */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
@ -12,6 +12,8 @@ export type NotificationsSchemaItem = {
|
|||||||
id: number;
|
id: number;
|
||||||
/** The actual notification message */
|
/** The actual notification message */
|
||||||
message: string;
|
message: string;
|
||||||
|
/** The link to change request or feature toggle the notification refers to */
|
||||||
|
link?: string;
|
||||||
/** The type of the notification used e.g. for the graphical hints */
|
/** The type of the notification used e.g. for the graphical hints */
|
||||||
notificationType: NotificationsSchemaItemNotificationType;
|
notificationType: NotificationsSchemaItemNotificationType;
|
||||||
createdBy: NotificationsSchemaItemCreatedBy;
|
createdBy: NotificationsSchemaItemCreatedBy;
|
||||||
|
Loading…
Reference in New Issue
Block a user