Skip to content

Commit ceb3d3e

Browse files
authored
Merge pull request #231 from SaqAsh/feature/custom-banner
add system alerts banner component in order to display alerts via jso…
2 parents a5ced86 + 56c64b0 commit ceb3d3e

File tree

8 files changed

+293
-3
lines changed

8 files changed

+293
-3
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved
4+
*
5+
* This program and the accompanying materials are made available under the terms of
6+
* the GNU Affero General Public License v3.0. You should have received a copy of the
7+
* GNU Affero General Public License along with this program.
8+
* If not, see <http://www.gnu.org/licenses/>.
9+
*
10+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
16+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
17+
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
18+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*
20+
*/
21+
22+
import { AlertDef } from '@/components/SystemAlerts/types';
23+
import { default as theme } from '@/components/theme';
24+
import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons';
25+
import Dismiss from '@/components/theme/icons/dismiss';
26+
import { css } from '@emotion/react';
27+
import React from 'react';
28+
29+
type Props = {
30+
alert: AlertDef;
31+
onClose: () => void;
32+
};
33+
34+
const AlertVariants = {
35+
critical: {
36+
backgroundColor: theme.colors.error_2,
37+
icon: <ErrorIcon height={26} width={26} />,
38+
textColor: theme.colors.black,
39+
outline: theme.colors.error_dark,
40+
},
41+
warning: {
42+
backgroundColor: theme.colors.warning_light,
43+
icon: <Warning height={26} width={26} />,
44+
textColor: theme.colors.black,
45+
outline: theme.colors.warning_dark,
46+
},
47+
info: {
48+
backgroundColor: theme.colors.secondary_1,
49+
icon: <Info fill={theme.colors.secondary_accessible} />,
50+
textColor: theme.colors.black,
51+
outline: theme.colors.secondary_dark,
52+
},
53+
};
54+
55+
export const SystemAlert: React.FC<Props> = ({ alert, onClose }) => {
56+
const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level];
57+
58+
return (
59+
<div
60+
css={css`
61+
padding: 12px;
62+
display: flex;
63+
justify-content: space-between;
64+
background-color: ${backgroundColor};
65+
border-bottom: 1px solid ${outline};
66+
`}
67+
>
68+
<div
69+
css={css`
70+
display: flex;
71+
`}
72+
>
73+
<div
74+
css={css`
75+
margin: auto 15px auto auto;
76+
`}
77+
>
78+
{icon}
79+
</div>
80+
<div>
81+
<div
82+
css={css`
83+
color: ${textColor};
84+
margin-top: ${alert.message ? '0px' : '6px'};
85+
${theme.typography.heading};
86+
`}
87+
>
88+
{alert.title}
89+
</div>
90+
{alert.message && (
91+
<div
92+
css={css`
93+
color: ${textColor};
94+
margin-bottom: 8px;
95+
${theme.typography.regular};
96+
`}
97+
dangerouslySetInnerHTML={{ __html: alert.message }}
98+
/>
99+
)}
100+
</div>
101+
</div>
102+
{alert.dismissable && (
103+
<div
104+
css={css`
105+
cursor: pointer;
106+
`}
107+
onClick={onClose}
108+
>
109+
<Dismiss height={15} width={15} fill={theme.colors.black} />
110+
</div>
111+
)}
112+
</div>
113+
);
114+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved
4+
*
5+
* This program and the accompanying materials are made available under the terms of
6+
* the GNU Affero General Public License v3.0. You should have received a copy of the
7+
* GNU Affero General Public License along with this program.
8+
* If not, see <http://www.gnu.org/licenses/>.
9+
*
10+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
16+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
17+
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
18+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*
20+
*/
21+
22+
import { getConfig } from '@/global/config';
23+
import React, { useEffect, useState } from 'react';
24+
import { SystemAlert } from '@/components/SystemAlerts/SystemAlert';
25+
import { AlertDef, isAlertDefs } from '@/components/SystemAlerts/types';
26+
27+
const LOCAL_STORAGE_KEY = 'SYSTEM_ALERTS_DISMISSED_IDS';
28+
29+
type Props = {
30+
alerts?: AlertDef[];
31+
};
32+
33+
export const SystemAlerts: React.ComponentType<Props> = ({ alerts }) => {
34+
const [displayAlerts, setDisplayAlerts] = useState<AlertDef[]>([]);
35+
const [dismissedIds, setDismissedIds] = useState<string[]>([]);
36+
37+
const getParsedSystemAlerts = () => {
38+
try {
39+
const { NEXT_PUBLIC_SYSTEM_ALERTS } = getConfig();
40+
const parsed = JSON.parse(NEXT_PUBLIC_SYSTEM_ALERTS);
41+
if (!isAlertDefs(parsed)) throw new Error('System Alert types are invalid!');
42+
return parsed;
43+
} catch (e) {
44+
console.error('Failed to parse systems alerts! Using empty array!', e);
45+
return [];
46+
}
47+
};
48+
49+
const systemAlerts = alerts ?? getParsedSystemAlerts();
50+
const systemAlertIds = systemAlerts.map((a) => a.id);
51+
52+
const handleClose = (id: string) => {
53+
const updated = dismissedIds.concat(id).filter((id) => systemAlertIds.includes(id));
54+
setDisplayAlerts(systemAlerts.filter((a) => !updated.includes(a.id)));
55+
setDismissedIds(updated);
56+
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(updated));
57+
};
58+
59+
useEffect(() => {
60+
const stored = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '[]');
61+
setDismissedIds(stored);
62+
setDisplayAlerts(systemAlerts.filter((a) => !stored.includes(a.id)));
63+
}, []);
64+
65+
return (
66+
<>
67+
{displayAlerts.map((alert) => (
68+
<SystemAlert key={alert.id} alert={alert} onClose={() => handleClose(alert.id)} />
69+
))}
70+
</>
71+
);
72+
};

components/SystemAlerts/types.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved
4+
*
5+
* This program and the accompanying materials are made available under the terms of
6+
* the GNU Affero General Public License v3.0. You should have received a copy of the
7+
* GNU Affero General Public License along with this program.
8+
* If not, see <http://www.gnu.org/licenses/>.
9+
*
10+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
16+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
17+
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
18+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*
20+
*/
21+
22+
export type AlertLevel = 'info' | 'warning' | 'critical';
23+
24+
export const ALERT_LEVELS = {
25+
info: 'info',
26+
warning: 'warning',
27+
critical: 'critical',
28+
} as const;
29+
30+
export type AlertDef = {
31+
level: AlertLevel;
32+
title: string;
33+
message?: string;
34+
dismissable: boolean;
35+
id: string;
36+
};
37+
38+
export const isAlertLevel = (level: any): level is AlertLevel => {
39+
return level === 'info' || level === 'warning' || level === 'critical';
40+
};
41+
42+
export const isAlertDef = (obj: any): obj is AlertDef => {
43+
return obj.id && obj.title && obj.dismissable !== undefined && isAlertLevel(obj.level);
44+
};
45+
46+
export const isAlertDefs = (obj: any): obj is AlertDef[] => {
47+
return Array.isArray(obj) && obj.every(isAlertDef);
48+
};

components/theme/colors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const error = {
7575
};
7676

7777
const warning = {
78+
warning_light: '#ffff758c',
7879
warning: '#f2d021',
7980
warning_dark: '#e6c104',
8081
};

components/theme/icons/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Checkmark from './checkmark';
3636
import Spinner from './spinner';
3737
import Error from './error';
3838
import Warning from './warning';
39+
import Info from './info';
3940

4041
export {
4142
GoogleLogo,
@@ -55,4 +56,5 @@ export {
5556
Spinner,
5657
Error,
5758
Warning,
59+
Info,
5860
};

components/theme/icons/info.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved
4+
*
5+
* This program and the accompanying materials are made available under the terms of
6+
* the GNU Affero General Public License v3.0. You should have received a copy of the
7+
* GNU Affero General Public License along with this program.
8+
* If not, see <http://www.gnu.org/licenses/>.
9+
*
10+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
16+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
17+
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
18+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19+
*
20+
*/
21+
22+
import { ReactElement } from 'react';
23+
import { css } from '@emotion/react';
24+
25+
import { IconProps } from './types';
26+
27+
const Info = ({ fill, size = 30, style }: IconProps): ReactElement => {
28+
return (
29+
<svg
30+
css={css`
31+
${style};
32+
height: ${size}px;
33+
width: ${size}px;
34+
`}
35+
width={size}
36+
height={size}
37+
viewBox="0 0 30 30"
38+
>
39+
<g fill="none" fillRule="evenodd">
40+
<g fill={fill}>
41+
<path d="M15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15C0 6.716 6.716 0 15 0z" />
42+
<path
43+
d="M14.62 10.985c1.104 0 2.019-.888 2.019-1.992S15.724 7 14.619 7c-1.103 0-2.019.889-2.019 1.993s.916 1.992 2.02 1.992zM12.6 21.083c0 1.023.916 1.858 2.02 1.858s2.019-.835 2.019-1.858v-7.217c0-1.023-.915-1.858-2.02-1.858-1.103 0-2.019.835-2.019 1.858v7.217z"
44+
fill="white"
45+
/>
46+
</g>
47+
</g>
48+
</svg>
49+
);
50+
};
51+
52+
export default Info;

components/theme/icons/warning.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { css } from '@emotion/react';
2323
import { IconProps } from './types';
2424
import theme from '../';
2525

26-
const Warning = ({ height, width, style, fill = theme.colors.error_dark }: IconProps) => {
26+
const Warning = ({ height, width, style, fill = theme.colors.warning_dark }: IconProps) => {
2727
return (
2828
<svg
2929
css={css`

global/config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@ export const getConfig = () => {
4242
`-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lOqMuPLCVusc6szklNXQL1FHhSkEgR7An+8BllBqTsRHM4bRYosseGFCbYPn8r8FsWuMDtxp0CwTyMQR2PCbJ740DdpbE1KC6jAfZxqcBete7gP0tooJtbvnA6X4vNpG4ukhtUoN9DzNOO0eqMU0Rgyy5HjERdYEWkwTNB30i9I+nHFOSj4MGLBSxNlnuo3keeomCRgtimCx+L/K3HNo0QHTG1J7RzLVAchfQT0lu3pUJ8kB+UM6/6NG+fVyysJyRZ9gadsr4gvHHckw8oUBp2tHvqBEkEdY+rt1Mf5jppt7JUV7HAPLB/qR5jhALY2FX/8MN+lPLmb/nLQQichVQIDAQAB\r\n-----END PUBLIC KEY-----`,
4343
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: publicConfig.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || '',
4444
NEXT_PUBLIC_KEYCLOAK_HOST: publicConfig.NEXT_PUBLIC_KEYCLOAK_HOST || '',
45-
NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE:
46-
publicConfig.NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE || '',
45+
NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE: publicConfig.NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE || '',
4746
NEXT_PUBLIC_KEYCLOAK_REALM: publicConfig.NEXT_PUBLIC_KEYCLOAK_REALM || '',
4847
NEXT_PUBLIC_LAB_NAME: publicConfig.NEXT_PUBLIC_LAB_NAME || 'Overture Stage UI',
4948
NEXT_PUBLIC_LOGO_FILENAME: publicConfig.NEXT_PUBLIC_LOGO_FILENAME,
5049
NEXT_PUBLIC_SSO_PROVIDERS: publicConfig.NEXT_PUBLIC_SSO_PROVIDERS || '',
5150
NEXT_PUBLIC_UI_VERSION: publicConfig.NEXT_PUBLIC_UI_VERSION || '',
5251
SESSION_ENCRYPTION_SECRET: process.env.SESSION_ENCRYPTION_SECRET || '',
52+
NEXT_PUBLIC_SYSTEM_ALERTS: process.env.NEXT_PUBLIC_SYSTEM_ALERTS || '',
5353
} as {
54+
NEXT_PUBLIC_SYSTEM_ALERTS: string;
5455
ACCESSTOKEN_ENCRYPTION_SECRET: string;
5556
KEYCLOAK_CLIENT_SECRET: string;
5657
NEXT_PUBLIC_ADMIN_EMAIL: string;

0 commit comments

Comments
 (0)