Repository: data-team-uhn/cards
Pull request #1961: Refactored the Downtime Banner and Demo Warning Banner components to reduce custom styling
Author: login: marta-
Labels: name: tested
description: Passed manual testing, needs code review
name: Merge me!
description: Tested, reviewed, approved, looks ready to merge
name: easy to review
description: Very few changes, very small time investment to review
Created: 2025-05-02T18:08:05Z | Merged: 2025-05-08T13:46:03Z

* Used an `Alert` instead of a Toolbar+Grid to display the icon on the side of the message in banners
* Specific to Downtime banner:
  * Shorter message
  * Fixed WSORD in case of an error (caused by attempt to display `errorText` which does not exist)

Before:

![image](https://github.com/user-attachments/assets/9fcac0a1-4ff0-47b4-a1f1-33a5fc06b064)

After:

![image](https://github.com/user-attachments/assets/50be97f3-cd0a-40e2-9608-d384b010cf49)

![image](https://github.com/user-attachments/assets/30a6de3b-cb6a-455c-97a5-d00c139102dd)

![image](https://github.com/user-attachments/assets/c5cbe877-c323-4d47-992f-921aa0324bc8)

![image](https://github.com/user-attachments/assets/db256337-7e7d-460e-87a6-280eeb380757)


Diff:
diff --git a/modules/demo-banner/src/main/frontend/src/demoBanner.jsx b/modules/demo-banner/src/main/frontend/src/demoBanner.jsx
index a12ed0c43f..0385bb8eff 100644
--- a/modules/demo-banner/src/main/frontend/src/demoBanner.jsx
+++ b/modules/demo-banner/src/main/frontend/src/demoBanner.jsx
@@ -20,41 +20,18 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning" sx={{justifyContent: "center"}}>
+        This installation is for demo purposes only.
+        Do not enter any real data / patient identifiable information.
+      </Alert>
+    </AppBar>
   );
 }
diff --git a/modules/downtime-warning-banner/src/main/frontend/src/downtimeBanner.jsx b/modules/downtime-warning-banner/src/main/frontend/src/downtimeBanner.jsx
index 97390dac5c..d9148712dd 100644
--- a/modules/downtime-warning-banner/src/main/frontend/src/downtimeBanner.jsx
+++ b/modules/downtime-warning-banner/src/main/frontend/src/downtimeBanner.jsx
@@ -19,42 +19,18 @@
 
 import React, { useState, useEffect } from "react";
 import {
+  Alert,
   AppBar,
-  Avatar,
-  Grid,
-  Toolbar,
-  Typography,
 } from '@mui/material';
-import { withStyles } from 'tss-react/mui';
-import { useTheme } from '@mui/material/styles';
 import BuildIcon from '@mui/icons-material/Build';
 
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.info.main,
-    "& .MuiAvatar-root" : {
-      backgroundColor: theme.palette.background.paper,
-      color: theme.palette.info.main
-    },
-    "& b": {
-      backgroundColor: theme.palette.action.selected,
-      padding: "2px 4px",
-      borderRadius: "2px",
-    }
-  }
-});
-
 export default function DowntimeWarning(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const appName = document.querySelector('meta[name="title"]')?.content;
-
   // The the configuration values specified by the Administration
   const [ enabled, setEnabled ] = useState(false);
   const [ fromDate, setFromDate ] = useState();
   const [ toDate, setToDate ] = useState();
   // Error message set when fetching the data from the server fails
   const [ error, setError ] = useState();
-  const theme = useTheme();
 
   // Load the configurations only once, upon initialization
   useEffect(() => {
@@ -89,18 +65,13 @@ export default function DowntimeWarning(props) {
   }
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      {error && <Typography color='error'>{errorText}</Typography>}
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><Avatar><BuildIcon/></Avatar></Grid>
-        <Grid>
-        <Typography variant="body2">
-          {appName} will be down for maintenance from <b>{fromDate}</b> to <b>{toDate}</b>. We appologize for the inconvenience this may cause.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      { error &&
+        <Alert variant="filled" square severity="error" sx={{justifyContent: "center"}}>{error}</Alert>
+      }
+      <Alert variant="filled" square severity="info" icon={<BuildIcon/>} sx={{justifyContent: "center"}}>
+        Scheduled Maintenance: {fromDate} - {toDate}
+      </Alert>
+    </AppBar>
   );
 }


Reviews:
user: login: veronikaslc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T19:38:14Z
user: login: marta-
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T19:42:11Z
user: login: veronikaslc
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T06:10:45Z
user: login: marta-
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:25:04Z
user: login: marta-
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:26:06Z
user: login: sdumitriu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T02:01:13Z
user: login: marta-
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T02:48:03Z
user: login: sdumitriu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:40:27Z

Review comments:
diff_hunk: @@ -20,41 +20,23 @@
 import React from "react";
 
 import {
+  Alert,
+  AlertTitle,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" severity="warning" square>
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: veronikaslc
body: Do we drop WarningIcon?
created_at: 2025-05-02T19:38:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,23 @@
 import React from "react";
 
 import {
+  Alert,
+  AlertTitle,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" severity="warning" square>
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: marta-
body: Afaik it's displayed by default in "warning" alerts. We only need to specify the icon if we want one that is different from the default.
created_at: 2025-05-02T19:42:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,20 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning">
+        This installation is for demo purposes only.
+        Data entered here can be accessed by anyone and is
+        periodically deleted. Do not enter any real
+        data / patient identifiable information.
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: marta-
body: ```suggestion
        This installation is for demo purposes only.
        Do not enter any real data / patient identifiable information.
```
created_at: 2025-05-07T22:23:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,18 +65,11 @@ export default function DowntimeWarning(props) {
   }
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      {error && <Typography color='error'>{errorText}</Typography>}
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><Avatar><BuildIcon/></Avatar></Grid>
-        <Grid>
-        <Typography variant="body2">
-          {appName} will be down for maintenance from <b>{fromDate}</b> to <b>{toDate}</b>. We appologize for the inconvenience this may cause.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      {error && <Alert variant="filled" square severity="error">{error}</Alert>}
path: modules/downtime-warning-banner/src/main/frontend/src/downtimeBanner.jsx
user: login: marta-
body: ```suggestion
      {error && <Alert variant="filled" square severity="error" sx={{justifyContent: "center"}}>{error}</Alert>}
```
created_at: 2025-05-07T22:24:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,20 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning">
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: marta-
body: ```suggestion
      <Alert variant="filled" square severity="warning" sx={{justifyContent: "center"}}>
```
created_at: 2025-05-07T22:24:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,20 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning">
+        This installation is for demo purposes only.
+        Data entered here can be accessed by anyone and is
+        periodically deleted. Do not enter any real
+        data / patient identifiable information.
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: marta-
body: ```suggestion
        This installation is for demo purposes only.
        Do not enter any real data / patient identifiable information.
```
created_at: 2025-05-07T22:26:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,18 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning" sx={{justifyContent: "center"}}>
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: sdumitriu
body: What is the `square` prop? I can't see it in the API docs.
created_at: 2025-05-08T01:59:40Z
author_association: MEMBER
diff_hunk: @@ -20,41 +20,18 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning" sx={{justifyContent: "center"}}>
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: marta-
body: https://mui.com/material-ui/react-alert/#anatomy : "The Alert Component is composed of **a root Paper component** (...) that houses an icon, a message and an optional action"

https://mui.com/material-ui/api/alert/#props: "Props of the Paper component are also available" (to use with the Alert component)

https://mui.com/material-ui/react-paper/#corners : "The Paper component features rounded corners by default. Add the `square` prop for square corners"
created_at: 2025-05-08T02:48:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,41 +20,18 @@
 import React from "react";
 
 import {
+  Alert,
   AppBar,
-  Grid,
-  Toolbar,
-  Typography
 } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-import { withStyles } from 'tss-react/mui';
-
-import WarningIcon from '@mui/icons-material/Warning';
-
-const appbarStyle = theme => ({
-  root: {
-    backgroundColor: theme.palette.warning.main
-  }
-});
 
 export default function DemoBanner(props) {
-  const StyledAppBar = withStyles(AppBar, appbarStyle);
-  const theme = useTheme();
 
   return (
-    <StyledAppBar position="fixed" style={props.style} ref={props.onRender}>
-      <Toolbar>
-      <Grid container spacing={1} justifyContent="center" alignItems="center" wrap="nowrap">
-        <Grid sx={{ minWidth: theme.spacing(6)}}><WarningIcon/></Grid>
-        <Grid>
-        <Typography variant="subtitle2">
-          This installation is for demo purposes only.
-          Data entered here can be accessed by anyone and is
-          periodically deleted. Do not enter any real
-          data / patient identifiable information.
-        </Typography>
-        </Grid>
-      </Grid>
-      </Toolbar>
-    </StyledAppBar>
+    <AppBar position="fixed" style={props.style} ref={props.onRender}>
+      <Alert variant="filled" square severity="warning" sx={{justifyContent: "center"}}>
path: modules/demo-banner/src/main/frontend/src/demoBanner.jsx
user: login: sdumitriu
body: Ah, I see.
created_at: 2025-05-08T13:40:27Z
author_association: MEMBER

PR comments:
user: login: veronikaslc
created_at: 2025-05-02T18:37:20Z
body: Should we also refactor `DemoBanner` in a same way to use `Alert` with `severity="warning"`?
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-02T19:00:49Z
body: > Should we also refactor `DemoBanner` in a same way to use `Alert` with `severity="warning"`?

Yes of course, once we figure out the offset issue and this approach is polished enough to replicate in the other banner.
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-02T19:32:11Z
body: The demo warning refactor would probably look like in commit 73cdf8f - not tested.
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-02T19:40:26Z
body: I'm considering changing the content of the Downtime Banner to `Scheduled maintenance: {fromDate} - {toDate}`. No fluff, no apology, no wasted vertical space. Thoughts?
author_association: CONTRIBUTOR
user: login: veronikaslc
created_at: 2025-05-02T19:42:12Z
body: Support, less text- clear message.
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-02T19:46:33Z
body: > I'm considering changing the content of the Downtime Banner to `Scheduled maintenance: {fromDate} - {toDate}`. No fluff, no apology, no wasted vertical space. Thoughts?

It would look like this:

![image](https://github.com/user-attachments/assets/569c668e-fef2-422d-8920-6922e7a1dba5)

Miraculously, there's no offset issue anymore, I suspect the AlertTitle was the cause.
author_association: CONTRIBUTOR
user: login: veronikaslc
created_at: 2025-05-02T19:49:36Z
body: Since it's short now, do you think we can make it centered?
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-02T20:03:28Z
body: That's custom styling (which I had reduced to zero) but ok. It would look like this:
![image](https://github.com/user-attachments/assets/aa4053bd-77dc-42c9-be37-4bd8719d8cb9)

author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-05T15:04:38Z
body: Rebased, cleaned up commit history, ready for review.
author_association: CONTRIBUTOR
user: login: veronikaslc
created_at: 2025-05-06T18:05:07Z
body: Tested.

I'd even shorten the demo message to "Demo site: do not enter any real data!" and center it.
author_association: CONTRIBUTOR
user: login: marta-
created_at: 2025-05-07T23:37:43Z
body: Rebased on the latest dev, squashed some commits for a cleaner history.
author_association: CONTRIBUTOR