From d797395d0490aeeec9bbdf7bcc557dd7deedeac4 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 14 May 2025 15:08:30 -0400 Subject: [PATCH 1/8] add system alerts banner component in order to display alerts via json + env variable --- components/SystemAlerts/SystemAlert.tsx | 95 ++++++++++++++++++++++++ components/SystemAlerts/SystemAlerts.tsx | 72 ++++++++++++++++++ components/SystemAlerts/types.ts | 27 +++++++ components/theme/colors.ts | 1 + components/theme/icons/index.tsx | 2 + components/theme/icons/info.tsx | 31 ++++++++ components/theme/icons/warning.tsx | 2 +- global/config.ts | 5 +- 8 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 components/SystemAlerts/SystemAlert.tsx create mode 100644 components/SystemAlerts/SystemAlerts.tsx create mode 100644 components/SystemAlerts/types.ts create mode 100644 components/theme/icons/info.tsx diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx new file mode 100644 index 00000000..c162a963 --- /dev/null +++ b/components/SystemAlerts/SystemAlert.tsx @@ -0,0 +1,95 @@ +import { default as defaultTheme, default as theme } from '@/components/theme'; +import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons'; +import Dismiss from '@/components/theme/icons/dismiss'; +import { css } from '@emotion/react'; +import React from 'react'; +import { AlertDef } from '@/components/SystemAlerts/types'; + +type Props = { + alert: AlertDef; + onClose: () => void; +}; + +const AlertVariants = { + critical: { + backgroundColor: defaultTheme.colors.error_2, + icon: , + textColor: defaultTheme.colors.black, + outline: defaultTheme.colors.error_dark, + }, + warning: { + backgroundColor: theme.colors.warning_light, + icon: , + textColor: defaultTheme.colors.black, + outline: defaultTheme.colors.warning_dark, + }, + info: { + backgroundColor: defaultTheme.colors.secondary_1, + icon: , + textColor: defaultTheme.colors.black, + outline: defaultTheme.colors.secondary_dark, + }, +}; + +export const SystemAlert: React.FC = ({ alert, onClose }) => { + const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level]; + + const createMarkup = (msg: string) => ({ __html: msg }); + + return ( +
+
+
+ {icon} +
+
+
+ {alert.title} +
+ {alert.message && ( +
+ )} +
+
+ {alert.dismissable && ( +
+ +
+ )} +
+ ); +}; diff --git a/components/SystemAlerts/SystemAlerts.tsx b/components/SystemAlerts/SystemAlerts.tsx new file mode 100644 index 00000000..5c6c7776 --- /dev/null +++ b/components/SystemAlerts/SystemAlerts.tsx @@ -0,0 +1,72 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +import { getConfig } from '@/global/config'; +import React, { useEffect, useState } from 'react'; +import { SystemAlert } from '@/components/SystemAlerts/SystemAlert'; +import { AlertDef, isAlertDefs } from '@/components/SystemAlerts/types'; + +const LOCAL_STORAGE_KEY = 'SYSTEM_ALERTS_DISMISSED_IDS'; + +type Props = { + alerts?: AlertDef[]; +}; + +export const SystemAlerts: React.ComponentType = ({ alerts }) => { + const [displayAlerts, setDisplayAlerts] = useState([]); + const [dismissedIds, setDismissedIds] = useState([]); + + const getParsedSystemAlerts = () => { + try { + const { NEXT_PUBLIC_SYSTEM_ALERTS } = getConfig(); + const parsed = JSON.parse(NEXT_PUBLIC_SYSTEM_ALERTS); + if (!isAlertDefs(parsed)) throw new Error('System Alert types are invalid!'); + return parsed; + } catch (e) { + console.error('Failed to parse systems alerts! Using empty array!', e); + return []; + } + }; + + const systemAlerts = alerts ?? getParsedSystemAlerts(); + const systemAlertIds = systemAlerts.map((a) => a.id); + + const handleClose = (id: string) => { + const updated = dismissedIds.concat(id).filter((id) => systemAlertIds.includes(id)); + setDisplayAlerts(systemAlerts.filter((a) => !updated.includes(a.id))); + setDismissedIds(updated); + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(updated)); + }; + + useEffect(() => { + const stored = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '[]'); + setDismissedIds(stored); + setDisplayAlerts(systemAlerts.filter((a) => !stored.includes(a.id))); + }, []); + + return ( + <> + {displayAlerts.map((alert) => ( + handleClose(alert.id)} /> + ))} + + ); +}; diff --git a/components/SystemAlerts/types.ts b/components/SystemAlerts/types.ts new file mode 100644 index 00000000..3315d139 --- /dev/null +++ b/components/SystemAlerts/types.ts @@ -0,0 +1,27 @@ +export type AlertLevel = 'info' | 'warning' | 'critical'; + +export const ALERT_LEVELS = { + info: 'info', + warning: 'warning', + critical: 'critical', +} as const; + +export type AlertDef = { + level: AlertLevel; + title: string; + message?: string; + dismissable: boolean; + id: string; +}; + +export const isAlertLevel = (level: any): level is AlertLevel => { + return level === 'info' || level === 'warning' || level === 'critical'; +}; + +export const isAlertDef = (obj: any): obj is AlertDef => { + return obj.id && obj.title && obj.dismissable !== undefined && isAlertLevel(obj.level); +}; + +export const isAlertDefs = (obj: any): obj is AlertDef[] => { + return Array.isArray(obj) && obj.every(isAlertDef); +}; diff --git a/components/theme/colors.ts b/components/theme/colors.ts index b68ade48..b7dbff31 100644 --- a/components/theme/colors.ts +++ b/components/theme/colors.ts @@ -75,6 +75,7 @@ const error = { }; const warning = { + warning_light: '#ffff758c', warning: '#f2d021', warning_dark: '#e6c104', }; diff --git a/components/theme/icons/index.tsx b/components/theme/icons/index.tsx index fa8c5795..ba42b15a 100644 --- a/components/theme/icons/index.tsx +++ b/components/theme/icons/index.tsx @@ -36,6 +36,7 @@ import Checkmark from './checkmark'; import Spinner from './spinner'; import Error from './error'; import Warning from './warning'; +import Info from './info'; export { GoogleLogo, @@ -55,4 +56,5 @@ export { Spinner, Error, Warning, + Info, }; diff --git a/components/theme/icons/info.tsx b/components/theme/icons/info.tsx new file mode 100644 index 00000000..6ffad9ed --- /dev/null +++ b/components/theme/icons/info.tsx @@ -0,0 +1,31 @@ +import { ReactElement } from 'react'; +import { css } from '@emotion/react'; + +import { IconProps } from './types'; + +const Info = ({ fill, size = 30, style }: IconProps): ReactElement => { + return ( + + + + + + + + + ); +}; + +export default Info; diff --git a/components/theme/icons/warning.tsx b/components/theme/icons/warning.tsx index bec6ee1d..5115b7ae 100644 --- a/components/theme/icons/warning.tsx +++ b/components/theme/icons/warning.tsx @@ -23,7 +23,7 @@ import { css } from '@emotion/react'; import { IconProps } from './types'; import theme from '../'; -const Warning = ({ height, width, style, fill = theme.colors.error_dark }: IconProps) => { +const Warning = ({ height, width, style, fill = theme.colors.warning_dark }: IconProps) => { return ( { `-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lOqMuPLCVusc6szklNXQL1FHhSkEgR7An+8BllBqTsRHM4bRYosseGFCbYPn8r8FsWuMDtxp0CwTyMQR2PCbJ740DdpbE1KC6jAfZxqcBete7gP0tooJtbvnA6X4vNpG4ukhtUoN9DzNOO0eqMU0Rgyy5HjERdYEWkwTNB30i9I+nHFOSj4MGLBSxNlnuo3keeomCRgtimCx+L/K3HNo0QHTG1J7RzLVAchfQT0lu3pUJ8kB+UM6/6NG+fVyysJyRZ9gadsr4gvHHckw8oUBp2tHvqBEkEdY+rt1Mf5jppt7JUV7HAPLB/qR5jhALY2FX/8MN+lPLmb/nLQQichVQIDAQAB\r\n-----END PUBLIC KEY-----`, NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: publicConfig.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || '', NEXT_PUBLIC_KEYCLOAK_HOST: publicConfig.NEXT_PUBLIC_KEYCLOAK_HOST || '', - NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE: - publicConfig.NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE || '', + NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE: publicConfig.NEXT_PUBLIC_KEYCLOAK_PERMISSION_AUDIENCE || '', NEXT_PUBLIC_KEYCLOAK_REALM: publicConfig.NEXT_PUBLIC_KEYCLOAK_REALM || '', NEXT_PUBLIC_LAB_NAME: publicConfig.NEXT_PUBLIC_LAB_NAME || 'Overture Stage UI', NEXT_PUBLIC_LOGO_FILENAME: publicConfig.NEXT_PUBLIC_LOGO_FILENAME, NEXT_PUBLIC_SSO_PROVIDERS: publicConfig.NEXT_PUBLIC_SSO_PROVIDERS || '', NEXT_PUBLIC_UI_VERSION: publicConfig.NEXT_PUBLIC_UI_VERSION || '', SESSION_ENCRYPTION_SECRET: process.env.SESSION_ENCRYPTION_SECRET || '', + NEXT_PUBLIC_SYSTEM_ALERTS: process.env.NEXT_PUBLIC_SYSTEM_ALERTS || '', } as { + NEXT_PUBLIC_SYSTEM_ALERTS: string; ACCESSTOKEN_ENCRYPTION_SECRET: string; KEYCLOAK_CLIENT_SECRET: string; NEXT_PUBLIC_ADMIN_EMAIL: string; From 231ed1432dc61ab6006a3e7fc0bf5cb9a8c59dc3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 15 May 2025 08:38:18 -0400 Subject: [PATCH 2/8] add copyright comments --- components/SystemAlerts/SystemAlert.tsx | 21 +++++++++++++++++++++ components/SystemAlerts/types.ts | 21 +++++++++++++++++++++ components/theme/icons/info.tsx | 21 +++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index c162a963..6a3096c1 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import { default as defaultTheme, default as theme } from '@/components/theme'; import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons'; import Dismiss from '@/components/theme/icons/dismiss'; diff --git a/components/SystemAlerts/types.ts b/components/SystemAlerts/types.ts index 3315d139..1faef7d5 100644 --- a/components/SystemAlerts/types.ts +++ b/components/SystemAlerts/types.ts @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + export type AlertLevel = 'info' | 'warning' | 'critical'; export const ALERT_LEVELS = { diff --git a/components/theme/icons/info.tsx b/components/theme/icons/info.tsx index 6ffad9ed..266cd5e1 100644 --- a/components/theme/icons/info.tsx +++ b/components/theme/icons/info.tsx @@ -1,3 +1,24 @@ +/* + * + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved + * + * This program and the accompanying materials are made available under the terms of + * the GNU Affero General Public License v3.0. You should have received a copy of the + * GNU Affero General Public License along with this program. + * If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + import { ReactElement } from 'react'; import { css } from '@emotion/react'; From 646606c18534401a28f374de6617dc06b0e3031e Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 15 May 2025 08:41:05 -0400 Subject: [PATCH 3/8] resolve import into one import for default theme --- components/SystemAlerts/SystemAlert.tsx | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index 6a3096c1..00c12aec 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -19,7 +19,7 @@ * */ -import { default as defaultTheme, default as theme } from '@/components/theme'; +import { default as theme } from '@/components/theme'; import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons'; import Dismiss from '@/components/theme/icons/dismiss'; import { css } from '@emotion/react'; @@ -33,22 +33,22 @@ type Props = { const AlertVariants = { critical: { - backgroundColor: defaultTheme.colors.error_2, + backgroundColor: theme.colors.error_2, icon: , - textColor: defaultTheme.colors.black, - outline: defaultTheme.colors.error_dark, + textColor: theme.colors.black, + outline: theme.colors.error_dark, }, warning: { backgroundColor: theme.colors.warning_light, icon: , - textColor: defaultTheme.colors.black, - outline: defaultTheme.colors.warning_dark, + textColor: theme.colors.black, + outline: theme.colors.warning_dark, }, info: { - backgroundColor: defaultTheme.colors.secondary_1, - icon: , - textColor: defaultTheme.colors.black, - outline: defaultTheme.colors.secondary_dark, + backgroundColor: theme.colors.secondary_1, + icon: , + textColor: theme.colors.black, + outline: theme.colors.secondary_dark, }, }; @@ -84,7 +84,7 @@ export const SystemAlert: React.FC = ({ alert, onClose }) => { css={css` color: ${textColor}; margin-top: ${alert.message ? '0px' : '6px'}; - ${defaultTheme.typography.heading}; + ${theme.typography.heading}; `} > {alert.title} @@ -94,7 +94,7 @@ export const SystemAlert: React.FC = ({ alert, onClose }) => { css={css` color: ${textColor}; margin-bottom: 8px; - ${defaultTheme.typography.regular}; + ${theme.typography.regular}; `} dangerouslySetInnerHTML={createMarkup(alert.message)} /> @@ -108,7 +108,7 @@ export const SystemAlert: React.FC = ({ alert, onClose }) => { `} onClick={onClose} > - +
)} From 56c64b0cea03754e3f3600e3a83bce96fc56f34d Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Thu, 15 May 2025 08:48:46 -0400 Subject: [PATCH 4/8] change function to inline html command --- components/SystemAlerts/SystemAlert.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index 00c12aec..4dfe9b28 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -19,12 +19,12 @@ * */ +import { AlertDef } from '@/components/SystemAlerts/types'; import { default as theme } from '@/components/theme'; import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons'; import Dismiss from '@/components/theme/icons/dismiss'; import { css } from '@emotion/react'; import React from 'react'; -import { AlertDef } from '@/components/SystemAlerts/types'; type Props = { alert: AlertDef; @@ -55,8 +55,6 @@ const AlertVariants = { export const SystemAlert: React.FC = ({ alert, onClose }) => { const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level]; - const createMarkup = (msg: string) => ({ __html: msg }); - return (
= ({ alert, onClose }) => { margin-bottom: 8px; ${theme.typography.regular}; `} - dangerouslySetInnerHTML={createMarkup(alert.message)} + dangerouslySetInnerHTML={{ __html: alert.message }} /> )}
From a19c9749bfd4bcca59b16d3804a0f5be4f6a1de8 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 30 Jul 2025 14:19:57 -0400 Subject: [PATCH 5/8] types are usually a dumping ground for types that we may need or not need, hence remvoed and put into appropariate files --- components/SystemAlerts/SystemAlert.tsx | 104 ++++++++++++++--------- components/SystemAlerts/SystemAlerts.tsx | 22 ++++- components/SystemAlerts/types.ts | 48 ----------- 3 files changed, 82 insertions(+), 92 deletions(-) delete mode 100644 components/SystemAlerts/types.ts diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index 4dfe9b28..7b21f7a8 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -19,18 +19,54 @@ * */ -import { AlertDef } from '@/components/SystemAlerts/types'; +import { css } from '@emotion/react'; + import { default as theme } from '@/components/theme'; import { Error as ErrorIcon, Info, Warning } from '@/components/theme/icons'; import Dismiss from '@/components/theme/icons/dismiss'; -import { css } from '@emotion/react'; -import React from 'react'; + +export type AlertLevel = 'info' | 'warning' | 'critical'; + +export type AlertDef = { + level: AlertLevel; + title: string; + message?: string; + dismissible: boolean; + id: string; +}; type Props = { alert: AlertDef; onClose: () => void; }; +const alertContainerStyle = css` + padding: 12px; + display: flex; + justify-content: space-between; +`; + +const contentWrapperStyle = css` + display: flex; +`; + +const iconContainerStyle = css` + margin: auto 15px auto auto; +`; + +const titleStyle = css` + ${theme.typography.heading}; +`; + +const messageStyle = css` + margin-bottom: 8px; + ${theme.typography.regular}; +`; + +const dismissButtonStyle = css` + cursor: pointer; +`; + const AlertVariants = { critical: { backgroundColor: theme.colors.error_2, @@ -52,60 +88,48 @@ const AlertVariants = { }, }; -export const SystemAlert: React.FC = ({ alert, onClose }) => { +export const SystemAlert = ({ alert, onClose }: Props) => { const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level]; return (
-
-
- {icon} -
+
+
{icon}
{alert.title}
{alert.message && (
)}
- {alert.dismissable && ( -
+ {alert.dismissible && ( +
)} diff --git a/components/SystemAlerts/SystemAlerts.tsx b/components/SystemAlerts/SystemAlerts.tsx index 5c6c7776..54ae0a2f 100644 --- a/components/SystemAlerts/SystemAlerts.tsx +++ b/components/SystemAlerts/SystemAlerts.tsx @@ -19,10 +19,22 @@ * */ -import { getConfig } from '@/global/config'; import React, { useEffect, useState } from 'react'; -import { SystemAlert } from '@/components/SystemAlerts/SystemAlert'; -import { AlertDef, isAlertDefs } from '@/components/SystemAlerts/types'; + +import { SystemAlert, AlertDef, AlertLevel } from '@/components/SystemAlerts/SystemAlert'; +import { getConfig } from '@/global/config'; + +export const isAlertLevel = (level: any): level is AlertLevel => { + return level === 'info' || level === 'warning' || level === 'critical'; +}; + +export const isAlertDef = (obj: any): obj is AlertDef => { + return obj.id && obj.title && obj.dismissible !== undefined && isAlertLevel(obj.level); +}; + +export const isAlertDefs = (obj: any): obj is AlertDef[] => { + return Array.isArray(obj) && obj.every(isAlertDef); +}; const LOCAL_STORAGE_KEY = 'SYSTEM_ALERTS_DISMISSED_IDS'; @@ -38,7 +50,9 @@ export const SystemAlerts: React.ComponentType = ({ alerts }) => { try { const { NEXT_PUBLIC_SYSTEM_ALERTS } = getConfig(); const parsed = JSON.parse(NEXT_PUBLIC_SYSTEM_ALERTS); - if (!isAlertDefs(parsed)) throw new Error('System Alert types are invalid!'); + if (!isAlertDefs(parsed)) { + throw new Error('System Alert types are invalid!'); + } return parsed; } catch (e) { console.error('Failed to parse systems alerts! Using empty array!', e); diff --git a/components/SystemAlerts/types.ts b/components/SystemAlerts/types.ts deleted file mode 100644 index 1faef7d5..00000000 --- a/components/SystemAlerts/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * - * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved - * - * This program and the accompanying materials are made available under the terms of - * the GNU Affero General Public License v3.0. You should have received a copy of the - * GNU Affero General Public License along with this program. - * If not, see . - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -export type AlertLevel = 'info' | 'warning' | 'critical'; - -export const ALERT_LEVELS = { - info: 'info', - warning: 'warning', - critical: 'critical', -} as const; - -export type AlertDef = { - level: AlertLevel; - title: string; - message?: string; - dismissable: boolean; - id: string; -}; - -export const isAlertLevel = (level: any): level is AlertLevel => { - return level === 'info' || level === 'warning' || level === 'critical'; -}; - -export const isAlertDef = (obj: any): obj is AlertDef => { - return obj.id && obj.title && obj.dismissable !== undefined && isAlertLevel(obj.level); -}; - -export const isAlertDefs = (obj: any): obj is AlertDef[] => { - return Array.isArray(obj) && obj.every(isAlertDef); -}; From 8669e73631d3fd45953545495e86967d92a1a61b Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 30 Jul 2025 14:24:48 -0400 Subject: [PATCH 6/8] remove all inline html --- components/SystemAlerts/SystemAlert.tsx | 45 ++++++------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index 7b21f7a8..cb8605de 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -40,10 +40,12 @@ type Props = { onClose: () => void; }; -const alertContainerStyle = css` +const alertContainerStyle = (backgroundColor: string, outline: string) => css` padding: 12px; display: flex; justify-content: space-between; + background-color: ${backgroundColor}; + border-bottom: 1px solid ${outline}; `; const contentWrapperStyle = css` @@ -54,11 +56,14 @@ const iconContainerStyle = css` margin: auto 15px auto auto; `; -const titleStyle = css` +const titleStyle = (textColor: string, hasMessage: boolean) => css` + color: ${textColor}; + margin-top: ${hasMessage ? '0px' : '6px'}; ${theme.typography.heading}; `; -const messageStyle = css` +const messageStyle = (textColor: string) => css` + color: ${textColor}; margin-bottom: 8px; ${theme.typography.regular}; `; @@ -92,40 +97,12 @@ export const SystemAlert = ({ alert, onClose }: Props) => { const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level]; return ( -
+
{icon}
-
- {alert.title} -
- {alert.message && ( -
- )} +
{alert.title}
+ {alert.message &&
}
{alert.dismissible && ( From 30f8967f1d63030714e792026d54f38cc35e38f3 Mon Sep 17 00:00:00 2001 From: Saqib Ashraf Date: Wed, 30 Jul 2025 14:43:46 -0400 Subject: [PATCH 7/8] add ts doc --- components/SystemAlerts/SystemAlert.tsx | 6 ++++++ components/SystemAlerts/SystemAlerts.tsx | 11 ++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/components/SystemAlerts/SystemAlert.tsx b/components/SystemAlerts/SystemAlert.tsx index cb8605de..4db4a376 100644 --- a/components/SystemAlerts/SystemAlert.tsx +++ b/components/SystemAlerts/SystemAlert.tsx @@ -93,6 +93,12 @@ const AlertVariants = { }, }; +/** + * Renders a single system alert with appropriate styling based on alert level + * @param alert - Alert definition containing level, title, message, dismissible flag, and id + * @param onClose - Callback function triggered when the alert is dismissed + * @returns JSX element representing the styled alert + */ export const SystemAlert = ({ alert, onClose }: Props) => { const { backgroundColor, icon, textColor, outline } = AlertVariants[alert.level]; diff --git a/components/SystemAlerts/SystemAlerts.tsx b/components/SystemAlerts/SystemAlerts.tsx index 54ae0a2f..1f516e53 100644 --- a/components/SystemAlerts/SystemAlerts.tsx +++ b/components/SystemAlerts/SystemAlerts.tsx @@ -19,9 +19,9 @@ * */ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; -import { SystemAlert, AlertDef, AlertLevel } from '@/components/SystemAlerts/SystemAlert'; +import { AlertDef, AlertLevel, SystemAlert } from '@/components/SystemAlerts/SystemAlert'; import { getConfig } from '@/global/config'; export const isAlertLevel = (level: any): level is AlertLevel => { @@ -42,7 +42,12 @@ type Props = { alerts?: AlertDef[]; }; -export const SystemAlerts: React.ComponentType = ({ alerts }) => { +/** + * Manages and displays a collection of system alerts with dismissal functionality + * @param alerts - Optional array of alert definitions to display (falls back to config if not provided) + * @returns JSX element containing rendered system alerts + */ +export const SystemAlerts = ({ alerts }: Props) => { const [displayAlerts, setDisplayAlerts] = useState([]); const [dismissedIds, setDismissedIds] = useState([]); From fd7a7a6233c46a053d7d7703e5ee67c65a5dd0ec Mon Sep 17 00:00:00 2001 From: Mitchell Shiell Date: Fri, 16 Jan 2026 14:08:13 -0500 Subject: [PATCH 8/8] npm i next@^12 --- package-lock.json | 162 +++++----------------------------------------- package.json | 2 +- 2 files changed, 18 insertions(+), 146 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcb8b54f..ee95be2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", - "next": "^12.1.6", + "next": "^12.3.7", "next-auth": "^4.23.1", "next-compose-plugins": "^2.2.1", "next-global-css": "1.3.1", @@ -1509,9 +1509,10 @@ } }, "node_modules/@next/env": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.4.tgz", - "integrity": "sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A==" + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.7.tgz", + "integrity": "sha512-gCw4sTeHoNr0EUO+Nk9Ll21OzF3PnmM0GlHaKgsY2AWQSqQlMgECvB0YI4k21M9iGy+tQ5RMyXQuoIMpzhtxww==", + "license": "MIT" }, "node_modules/@next/swc-android-arm-eabi": { "version": "12.3.4", @@ -2400,14 +2401,6 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3038,14 +3031,6 @@ "node": ">=10" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3204,17 +3189,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -6789,14 +6763,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -6942,11 +6908,12 @@ "peer": true }, "node_modules/next": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.4.tgz", - "integrity": "sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==", + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.7.tgz", + "integrity": "sha512-3PDn+u77s5WpbkUrslBP6SKLMeUj9cSx251LOt+yP9fgnqXV/ydny81xQsclz9R6RzCLONMCtwK2RvDdLa/mJQ==", + "license": "MIT", "dependencies": { - "@next/env": "12.3.4", + "@next/env": "12.3.7", "@swc/helpers": "0.4.11", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -8778,34 +8745,6 @@ "node": ">=8" } }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, "node_modules/ts-patch": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-2.1.0.tgz", @@ -9450,17 +9389,6 @@ "engines": { "node": ">=10" } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } } }, "dependencies": { @@ -10574,9 +10502,9 @@ } }, "@next/env": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.4.tgz", - "integrity": "sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A==" + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.7.tgz", + "integrity": "sha512-gCw4sTeHoNr0EUO+Nk9Ll21OzF3PnmM0GlHaKgsY2AWQSqQlMgECvB0YI4k21M9iGy+tQ5RMyXQuoIMpzhtxww==" }, "@next/swc-android-arm-eabi": { "version": "12.3.4", @@ -11270,14 +11198,6 @@ "picomatch": "^2.0.4" } }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -11780,14 +11700,6 @@ "yaml": "^1.10.0" } }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -11909,14 +11821,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -14660,14 +14564,6 @@ "semver": "^6.0.0" } }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "optional": true, - "peer": true - }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -14774,11 +14670,11 @@ "peer": true }, "next": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.4.tgz", - "integrity": "sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==", + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.7.tgz", + "integrity": "sha512-3PDn+u77s5WpbkUrslBP6SKLMeUj9cSx251LOt+yP9fgnqXV/ydny81xQsclz9R6RzCLONMCtwK2RvDdLa/mJQ==", "requires": { - "@next/env": "12.3.4", + "@next/env": "12.3.7", "@next/swc-android-arm-eabi": "12.3.4", "@next/swc-android-arm64": "12.3.4", "@next/swc-darwin-arm64": "12.3.4", @@ -16142,22 +16038,6 @@ "punycode": "^2.1.1" } }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, "ts-patch": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-2.1.0.tgz", @@ -16632,14 +16512,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true } } } diff --git a/package.json b/package.json index b39d22f9..4483c599 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", - "next": "^12.1.6", + "next": "^12.3.7", "next-auth": "^4.23.1", "next-compose-plugins": "^2.2.1", "next-global-css": "1.3.1",