-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
frontend: Support for Workflow / Entire App Notification (#3084)
### Description Support for Notifications across the app. We have 3 types of notifications: 1. **Header**: displayed in the header of the page with dynamic message, link and severity. 2. **Per Workflow:** displayed at the top of the main section of the page for each workflow specified in the configuration of the notifications with dynamic title, message, link and severity per workflow. 3. **Multi Workflow**: displayed at the top of the main section of the page for the workflows specified in the configuration of the notifications with dynamic title, message, link and severity. ### Examples - Header and Per Workflow notification <img width="864" alt="Screenshot 2024-08-26 at 2 57 54 p m" src="https://github.com/user-attachments/assets/4905476d-68da-4752-8148-4cde449fa449"> - Header and Multi Workflow notification <img width="1722" alt="Screenshot 2024-08-26 at 2 59 53 p m" src="https://github.com/user-attachments/assets/ccaa97a6-5182-438f-8510-44c54c2b872a"> ### Testing You can use an object similar to this to test: `const banners = { header: { message: "header message", linkText: "link", link: "linkhere", severity: "info", }, perWorkflow: { workflowName1: { title: "per workflow title for test 2", message: "per workflow message for test 2", linkText: "link", link: "linkhere", severity: "info", }, workflowName2: { title: "per workflow title for test 3", message: "per workflow message for test 3", linkText: "link", link: "linkhere", severity: "info", }, }, multiWorkflow: { title: "multi workflow title", message: "multi workflow message", workflows: ["workflowName1", "workflowName3"], severity: "info", linkText: "link", link: "linkhere", }, };` Send this const as _**banners**_ in the _**appConfiguration**_ prop of your _**ClutchApp**_ --------- Co-authored-by: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com>
- Loading branch information
1 parent
7da29eb
commit 008ef58
Showing
13 changed files
with
409 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
frontend/packages/core/src/AppNotifications/HeaderNotification.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from "react"; | ||
import styled from "@emotion/styled"; | ||
import isEmpty from "lodash/isEmpty"; | ||
|
||
import { Alert } from "../Feedback"; | ||
import Grid from "../grid"; | ||
import { Link as LinkComponent } from "../link"; | ||
import type { AppBanners } from "../Types"; | ||
|
||
const StyledAlert = styled(Alert)({ | ||
padding: "8px 16px 8px 16px", | ||
justifyContent: "center", | ||
alignItems: "center", | ||
}); | ||
|
||
const StyledAlertContent = styled.div({ | ||
display: "flex", | ||
maxHeight: "40px", | ||
overflowY: "auto", | ||
}); | ||
|
||
const StyledMessage = styled.div({ | ||
flexWrap: "wrap", | ||
}); | ||
|
||
const StyledLink = styled.div({ | ||
marginLeft: "10px", | ||
}); | ||
|
||
interface HeaderNotificationProps { | ||
bannersData: AppBanners; | ||
onDismissAlert: (updatedData: AppBanners) => void; | ||
} | ||
|
||
const HeaderNotification = ({ bannersData, onDismissAlert }: HeaderNotificationProps) => { | ||
const headerBannerData = bannersData?.header; | ||
|
||
const onDismissAlertHeader = () => { | ||
onDismissAlert({ | ||
...bannersData, | ||
header: { | ||
...headerBannerData, | ||
dismissed: true, | ||
}, | ||
}); | ||
}; | ||
|
||
return ( | ||
<> | ||
{!isEmpty(headerBannerData) && !headerBannerData?.dismissed && ( | ||
<Grid item xs={4}> | ||
<StyledAlert | ||
severity={headerBannerData?.severity || "info"} | ||
elevation={6} | ||
onClose={onDismissAlertHeader} | ||
> | ||
<StyledAlertContent> | ||
<StyledMessage>{headerBannerData.message}</StyledMessage> | ||
{headerBannerData?.link && headerBannerData?.linkText && ( | ||
<StyledLink> | ||
<LinkComponent href={headerBannerData?.link}> | ||
{headerBannerData?.linkText} | ||
</LinkComponent> | ||
</StyledLink> | ||
)} | ||
</StyledAlertContent> | ||
</StyledAlert> | ||
</Grid> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default HeaderNotification; |
99 changes: 99 additions & 0 deletions
99
frontend/packages/core/src/AppNotifications/LayoutWithNotifications.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React from "react"; | ||
import isEmpty from "lodash/isEmpty"; | ||
|
||
import { Alert } from "../Feedback"; | ||
import Grid from "../grid"; | ||
import { Link as LinkComponent } from "../link"; | ||
import type { AppBanners } from "../Types"; | ||
|
||
interface LayoutWithNotificationsProps { | ||
bannersData: AppBanners; | ||
onDismissAlert: (updatedData: AppBanners) => void; | ||
children: React.ReactNode; | ||
workflow?: string; | ||
} | ||
|
||
const LayoutWithNotifications = ({ | ||
bannersData, | ||
onDismissAlert, | ||
children, | ||
workflow, | ||
}: LayoutWithNotificationsProps) => { | ||
const perWorkflowData = bannersData?.perWorkflow; | ||
const multiWorkflowData = bannersData?.multiWorkflow; | ||
|
||
const showAlertPerWorkflow = | ||
workflow && perWorkflowData[workflow] && !perWorkflowData[workflow]?.dismissed; | ||
const showAlertMultiWorkflow = | ||
showAlertPerWorkflow || perWorkflowData[workflow]?.dismissed | ||
? false | ||
: workflow && | ||
multiWorkflowData?.workflows?.includes(workflow) && | ||
!multiWorkflowData?.dismissed; | ||
|
||
const onDismissAlertPerWorkflow = () => { | ||
onDismissAlert({ | ||
...bannersData, | ||
perWorkflow: { | ||
...perWorkflowData, | ||
[workflow]: { ...perWorkflowData[workflow], dismissed: true }, | ||
}, | ||
}); | ||
}; | ||
|
||
const onDismissAlertMultiWorkflow = () => { | ||
onDismissAlert({ | ||
...bannersData, | ||
multiWorkflow: { | ||
...multiWorkflowData, | ||
dismissed: true, | ||
}, | ||
}); | ||
}; | ||
|
||
const showContainer = !isEmpty(perWorkflowData) || !isEmpty(multiWorkflowData); | ||
|
||
return ( | ||
<> | ||
{showContainer && ( | ||
<Grid container justifyContent="center" pt={2} pb={1} px={3}> | ||
<Grid item xs> | ||
{showAlertPerWorkflow && ( | ||
<Alert | ||
severity={perWorkflowData[workflow]?.severity || "info"} | ||
title={perWorkflowData[workflow]?.title} | ||
elevation={6} | ||
onClose={onDismissAlertPerWorkflow} | ||
> | ||
{perWorkflowData[workflow]?.message} | ||
{perWorkflowData[workflow]?.link && perWorkflowData[workflow]?.linkText && ( | ||
<LinkComponent href={perWorkflowData[workflow]?.link}> | ||
{perWorkflowData[workflow]?.linkText} | ||
</LinkComponent> | ||
)} | ||
</Alert> | ||
)} | ||
{showAlertMultiWorkflow && !showAlertPerWorkflow && ( | ||
<Alert | ||
severity={multiWorkflowData?.severity || "info"} | ||
title={multiWorkflowData?.title} | ||
elevation={6} | ||
onClose={onDismissAlertMultiWorkflow} | ||
> | ||
{multiWorkflowData?.message} | ||
{multiWorkflowData?.link && multiWorkflowData?.linkText && ( | ||
<LinkComponent href={multiWorkflowData?.link}> | ||
{multiWorkflowData?.linkText} | ||
</LinkComponent> | ||
)} | ||
</Alert> | ||
)} | ||
</Grid> | ||
</Grid> | ||
)} | ||
{children} | ||
</> | ||
); | ||
}; | ||
|
||
export default LayoutWithNotifications; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import React, { useEffect } from "react"; | ||
|
||
import type { AppBanners } from "../Types"; | ||
|
||
import HeaderNotification from "./HeaderNotification"; | ||
import LayoutWithNotifications from "./LayoutWithNotifications"; | ||
import compareAppNotificationsData from "./useCompareAppNotificationsData"; | ||
|
||
interface AppNotificationProps { | ||
type: "header" | "layout"; | ||
banners: AppBanners; | ||
workflow?: string; | ||
children?: React.ReactNode; | ||
} | ||
|
||
const AppNotification = ({ type, banners, children, workflow }: AppNotificationProps) => { | ||
const { shouldUpdate, bannersData, dispatch } = compareAppNotificationsData(banners); | ||
|
||
useEffect(() => { | ||
if (shouldUpdate) { | ||
dispatch({ | ||
type: "SetPref", | ||
payload: { | ||
key: "banners", | ||
value: bannersData, | ||
}, | ||
}); | ||
} | ||
}, [shouldUpdate]); | ||
|
||
const onDismissAlert = (updatedData: AppBanners) => { | ||
dispatch({ | ||
type: "SetPref", | ||
payload: { | ||
key: "banners", | ||
value: updatedData, | ||
}, | ||
}); | ||
}; | ||
|
||
return type === "header" ? ( | ||
<HeaderNotification bannersData={bannersData as AppBanners} onDismissAlert={onDismissAlert} /> | ||
) : ( | ||
<LayoutWithNotifications | ||
workflow={workflow} | ||
bannersData={bannersData as AppBanners} | ||
onDismissAlert={onDismissAlert} | ||
> | ||
{children} | ||
</LayoutWithNotifications> | ||
); | ||
}; | ||
|
||
export default AppNotification; |
80 changes: 80 additions & 0 deletions
80
frontend/packages/core/src/AppNotifications/useCompareAppNotificationsData.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import get from "lodash/get"; | ||
import isEmpty from "lodash/isEmpty"; | ||
import isEqual from "lodash/isEqual"; | ||
|
||
import { useUserPreferences } from "../Contexts/preferences-context"; | ||
import type { AppBanners } from "../Types"; | ||
|
||
const useCompareAppNotificationsData = (banners: AppBanners) => { | ||
const { preferences, dispatch } = useUserPreferences(); | ||
const bannersPreferences: AppBanners = get(preferences, "banners"); | ||
|
||
const bannersData = { | ||
header: {}, | ||
multiWorkflow: {}, | ||
perWorkflow: {}, | ||
}; | ||
let shouldUpdate = false; | ||
|
||
if (!isEmpty(banners?.header)) { | ||
const headerPreferences = { | ||
message: bannersPreferences?.header?.message, | ||
linkText: bannersPreferences?.header.linkText, | ||
link: bannersPreferences?.header.link, | ||
severity: bannersPreferences?.header.severity, | ||
}; | ||
|
||
if (!isEqual(banners?.header, headerPreferences)) { | ||
bannersData.header = { ...banners?.header, dismissed: false }; | ||
shouldUpdate = true; | ||
} else { | ||
bannersData.header = { ...bannersPreferences?.header }; | ||
} | ||
} | ||
|
||
if (!isEmpty(banners?.multiWorkflow)) { | ||
const multiWorkflowPreferences = { | ||
title: bannersPreferences?.multiWorkflow?.title, | ||
message: bannersPreferences?.multiWorkflow?.message, | ||
workflows: bannersPreferences?.multiWorkflow.workflows, | ||
link: bannersPreferences?.multiWorkflow.link, | ||
linkText: bannersPreferences?.multiWorkflow.linkText, | ||
severity: bannersPreferences?.multiWorkflow.severity, | ||
}; | ||
|
||
if (!isEqual(banners?.multiWorkflow, multiWorkflowPreferences)) { | ||
bannersData.multiWorkflow = { ...banners?.multiWorkflow, dismissed: false }; | ||
shouldUpdate = true; | ||
} else { | ||
bannersData.multiWorkflow = { ...bannersPreferences?.multiWorkflow }; | ||
} | ||
} | ||
|
||
if (!isEmpty(banners?.perWorkflow)) { | ||
Object.keys(banners?.perWorkflow).forEach(key => { | ||
if (bannersPreferences?.perWorkflow?.[key]) { | ||
const perWorkflowPreferences = { | ||
title: bannersPreferences?.perWorkflow?.[key]?.title, | ||
message: bannersPreferences?.perWorkflow?.[key]?.message, | ||
linkText: bannersPreferences?.perWorkflow?.[key].linkText, | ||
link: bannersPreferences?.perWorkflow?.[key].link, | ||
severity: bannersPreferences?.perWorkflow?.[key].severity, | ||
}; | ||
|
||
if (!isEqual(banners?.perWorkflow?.[key], perWorkflowPreferences)) { | ||
bannersData.perWorkflow[key] = { ...banners?.perWorkflow?.[key], dismissed: false }; | ||
shouldUpdate = true; | ||
} else { | ||
bannersData.perWorkflow[key] = bannersPreferences?.perWorkflow?.[key]; | ||
} | ||
} else { | ||
bannersData.perWorkflow[key] = { ...banners?.perWorkflow?.[key], dismissed: false }; | ||
shouldUpdate = true; | ||
} | ||
}); | ||
} | ||
|
||
return { shouldUpdate, bannersData, dispatch }; | ||
}; | ||
|
||
export default useCompareAppNotificationsData; |
Oops, something went wrong.