diff --git a/frontend/src/component/common/MessageBanner/MessageBanner.tsx b/frontend/src/component/common/MessageBanner/MessageBanner.tsx new file mode 100644 index 0000000000..13e48083d3 --- /dev/null +++ b/frontend/src/component/common/MessageBanner/MessageBanner.tsx @@ -0,0 +1,139 @@ +import { 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'; + +const StyledBar = styled('aside', { + shouldForwardProp: prop => prop !== 'variant', +})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({ + position: 'relative', + zIndex: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: theme.spacing(1), + gap: theme.spacing(1), + borderBottom: '1px solid', + borderColor: theme.palette[variant].border, + background: theme.palette[variant].light, + color: theme.palette[variant].dark, +})); + +const StyledIcon = styled('div', { + shouldForwardProp: prop => prop !== 'variant', +})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({ + 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'; + +interface IMessageFlag { + enabled: boolean; + message: string; + variant?: BannerVariant; + icon?: string; + link?: string; + linkText?: string; + plausibleEvent?: string; +} + +// TODO: Grab a real feature flag instead +const mockFlag: IMessageFlag = { + enabled: true, + message: + 'Heads up! It seems like one of your client instances might be misbehaving.', + variant: 'warning', + link: '/admin/network', + linkText: 'View Network', + plausibleEvent: 'network_warning', +}; + +export const MessageBanner = () => { + const { uiConfig } = useUiConfig(); + + const { enabled, message, variant, icon, link, linkText, plausibleEvent } = + { ...mockFlag, enabled: uiConfig.flags.messageBanner }; + + if (!enabled) return null; + + return ( + + + + + + + + ); +}; + +interface IBannerIconProps { + icon?: string; + variant?: BannerVariant; +} + +const BannerIcon = ({ icon, variant }: IBannerIconProps) => { + if (icon === 'none') return null; + if (icon) return {icon}; + if (variant) return ; + // TODO: Add defaults for other variants? + return null; +}; + +interface IBannerButtonProps { + link?: string; + linkText?: string; + plausibleEvent?: string; +} + +const BannerButton = ({ + link, + linkText = 'More info', + plausibleEvent, +}: IBannerButtonProps) => { + if (!link) return null; + + const navigate = useNavigate(); + const tracker = usePlausibleTracker(); + const external = link.startsWith('http'); + + const trackEvent = () => { + if (!plausibleEvent) return; + tracker.trackEvent('message_banner', { + props: { event: plausibleEvent }, + }); + }; + + if (external) + return ( + + {linkText} + + ); + else + return ( + { + trackEvent(); + navigate(link); + }} + > + {linkText} + + ); +}; diff --git a/frontend/src/hooks/usePlausibleTracker.ts b/frontend/src/hooks/usePlausibleTracker.ts index da2fd5c3da..295e6bfbc1 100644 --- a/frontend/src/hooks/usePlausibleTracker.ts +++ b/frontend/src/hooks/usePlausibleTracker.ts @@ -13,7 +13,8 @@ type CustomEvents = | 'upgrade_plan_clicked' | 'change_request' | 'favorite' - | 'maintenance'; + | 'maintenance' + | 'message_banner'; export const usePlausibleTracker = () => { const plausible = useContext(PlausibleContext); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 5e28bea193..0b800d72b0 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -13,6 +13,7 @@ import { FeedbackCESProvider } from 'component/feedback/FeedbackCESContext/Feedb import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider'; import { InstanceStatus } from 'component/common/InstanceStatus/InstanceStatus'; import { UIProviderContainer } from 'component/providers/UIProvider/UIProviderContainer'; +import { MessageBanner } from 'component/common/MessageBanner/MessageBanner'; ReactDOM.render( @@ -22,6 +23,7 @@ ReactDOM.render( + diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index b7d6dbd0e3..ebc915cda1 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -46,6 +46,7 @@ export interface IFlags { variantsPerEnvironment?: boolean; networkView?: boolean; maintenance?: boolean; + messageBanner?: boolean; } export interface IVersionInfo { diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 10007de7d8..a70140e103 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -76,6 +76,7 @@ exports[`should create default config 1`] = ` "embedProxyFrontend": true, "maintenance": false, "maintenanceMode": false, + "messageBanner": false, "networkView": false, "proxyReturnAllToggles": false, "responseTimeWithAppName": false, @@ -93,6 +94,7 @@ exports[`should create default config 1`] = ` "embedProxyFrontend": true, "maintenance": false, "maintenanceMode": false, + "messageBanner": false, "networkView": false, "proxyReturnAllToggles": false, "responseTimeWithAppName": false, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 483f15bfd1..6189feafc1 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -47,6 +47,10 @@ const flags = { process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE_MODE, false, ), + messageBanner: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_MESSAGE_BANNER, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = {