Skip to content

Commit

Permalink
feat: adds an optional dialog and markdown support (#2728)
Browse files Browse the repository at this point in the history
Adds the option of showing a dialog and adds markdown support to it,
could be useful to show changelogs inside Unleash in the future.


![image](https://user-images.githubusercontent.com/14320932/209147164-ed19c9ab-fae5-452b-9aab-075d058ba10e.png)
  • Loading branch information
nunogois committed Dec 22, 2022
1 parent aaa96f7 commit 2d16730
Show file tree
Hide file tree
Showing 4 changed files with 627 additions and 40 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Expand Up @@ -85,6 +85,7 @@
"react-dom": "17.0.2",
"react-error-boundary": "3.1.4",
"react-hooks-global-state": "2.1.0",
"react-markdown": "^8.0.4",
"react-router-dom": "6.4.5",
"react-table": "7.8.0",
"react-test-renderer": "17.0.2",
Expand Down
137 changes: 103 additions & 34 deletions frontend/src/component/common/MessageBanner/MessageBanner.tsx
@@ -1,12 +1,15 @@
import { WarningAmber } from '@mui/icons-material';
import { Check, Close, InfoOutlined, WarningAmber } from '@mui/icons-material';
import { styled, Icon, Link } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useNavigate } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { MessageBannerDialog } from './MessageBannerDialog/MessageBannerDialog';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';

const StyledBar = styled('aside', {
shouldForwardProp: prop => prop !== 'variant',
})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({
})<{ variant: BannerVariant }>(({ theme, variant }) => ({
position: 'relative',
zIndex: 1,
display: 'flex',
Expand All @@ -18,25 +21,24 @@ const StyledBar = styled('aside', {
borderColor: theme.palette[variant].border,
background: theme.palette[variant].light,
color: theme.palette[variant].dark,
fontSize: theme.fontSizes.smallBody,
}));

const StyledIcon = styled('div', {
shouldForwardProp: prop => prop !== 'variant',
})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({
})<{ variant: BannerVariant }>(({ theme, variant }) => ({
display: 'flex',
alignItems: 'center',
color: theme.palette[variant].main,
}));

const StyledMessage = styled('div')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));

const StyledLink = styled(Link)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));

type BannerVariant = 'warning' | 'info' | 'error' | 'success' | 'neutral';
type BannerVariant =
| 'warning'
| 'info'
| 'error'
| 'success'
| 'neutral'
| 'secondary';

interface IMessageFlag {
enabled: boolean;
Expand All @@ -46,24 +48,59 @@ interface IMessageFlag {
link?: string;
linkText?: string;
plausibleEvent?: string;
dialogTitle?: string;
dialog?: string;
}

// TODO: Grab a real feature flag instead
const mockFlag: IMessageFlag = {
enabled: true,
message:
'<strong>Heads up!</strong> It seems like one of your client instances might be misbehaving.',
'**Heads up!** It seems like one of your client instances might be misbehaving.',
variant: 'warning',
link: '/admin/network',
linkText: 'View Network',
plausibleEvent: 'network_warning',
};

const mockFlag2: IMessageFlag = {
enabled: true,
message:
'**Unleash v5 is finally here!** Check out what changed in the newest major release.',
variant: 'secondary',
link: 'dialog',
linkText: "What's new?",
plausibleEvent: 'change_log_v5',
dialog: `![Unleash v5](https://www.getunleash.io/logos/unleash_pos.svg)
## Unleash v5 🎉
**Unleash v5 is finally here!**
Check out what changed in the newest major release:
- An Amazing Feature
- Another Amazing Feature
- We'll save the best for last
- And the best is...
- **Unleash v5 is finally here!**
You can read more about it on our newest [blog post](https://www.getunleash.io/blog).`,
};

export const MessageBanner = () => {
const { uiConfig } = useUiConfig();

const { enabled, message, variant, icon, link, linkText, plausibleEvent } =
{ ...mockFlag, enabled: uiConfig.flags.messageBanner };
const [open, setOpen] = useState(false);

const {
enabled,
message,
variant = 'neutral',
icon,
link,
linkText = 'More info',
plausibleEvent,
dialogTitle,
dialog,
} = { ...mockFlag2, enabled: uiConfig.flags.messageBanner };

if (!enabled) return null;

Expand All @@ -72,45 +109,65 @@ export const MessageBanner = () => {
<StyledIcon variant={variant}>
<BannerIcon icon={icon} variant={variant} />
</StyledIcon>
<StyledMessage dangerouslySetInnerHTML={{ __html: message }} />
<ReactMarkdown>{message}</ReactMarkdown>
<BannerButton
link={link}
linkText={linkText}
plausibleEvent={plausibleEvent}
/>
openDialog={() => setOpen(true)}
>
{linkText}
</BannerButton>
<MessageBannerDialog
open={open}
setOpen={setOpen}
title={dialogTitle || linkText}
>
{dialog!}
</MessageBannerDialog>
</StyledBar>
);
};

const VariantIcons = {
warning: <WarningAmber />,
info: <InfoOutlined />,
error: <Close />,
success: <Check />,
neutral: <InfoOutlined />,
secondary: <InfoOutlined />,
};

interface IBannerIconProps {
variant: BannerVariant;
icon?: string;
variant?: BannerVariant;
}

const BannerIcon = ({ icon, variant }: IBannerIconProps) => {
if (icon === 'none') return null;
if (icon) return <Icon>{icon}</Icon>;
if (variant) return <WarningAmber />;
// TODO: Add defaults for other variants?
return null;
return VariantIcons[variant] ?? <InfoOutlined />;
};

interface IBannerButtonProps {
link?: string;
linkText?: string;
plausibleEvent?: string;
openDialog: () => void;
children: React.ReactNode;
}

const BannerButton = ({
link,
linkText = 'More info',
plausibleEvent,
openDialog,
children,
}: IBannerButtonProps) => {
if (!link) return null;

const navigate = useNavigate();
const tracker = usePlausibleTracker();
const external = link.startsWith('http');

const dialog = link === 'dialog';
const internal = !link.startsWith('http');

const trackEvent = () => {
if (!plausibleEvent) return;
Expand All @@ -119,21 +176,33 @@ const BannerButton = ({
});
};

if (external)
if (dialog)
return (
<StyledLink href={link} target="_blank" onClick={trackEvent}>
{linkText}
</StyledLink>
<Link
onClick={() => {
trackEvent();
openDialog();
}}
>
{children}
</Link>
);
else

if (internal)
return (
<StyledLink
<Link
onClick={() => {
trackEvent();
navigate(link);
}}
>
{linkText}
</StyledLink>
{children}
</Link>
);

return (
<Link href={link} target="_blank" onClick={trackEvent}>
{children}
</Link>
);
};
@@ -0,0 +1,36 @@
import { styled } from '@mui/material';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import ReactMarkdown from 'react-markdown';

const StyledReactMarkdown = styled(ReactMarkdown)(({ theme }) => ({
'h1, h2, h3': {
margin: theme.spacing(2, 0),
},
}));

interface IMessageBannerDialogProps {
title: string;
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
children: string;
}

export const MessageBannerDialog = ({
open,
setOpen,
title,
children,
}: IMessageBannerDialogProps) => {
return (
<Dialogue
title={title}
open={open}
secondaryButtonText="Close"
onClose={() => {
setOpen(false);
}}
>
<StyledReactMarkdown>{children}</StyledReactMarkdown>
</Dialogue>
);
};

0 comments on commit 2d16730

Please sign in to comment.