From 6be8d600c660038cb63fc11e16e963c357e07f12 Mon Sep 17 00:00:00 2001 From: Wes Date: Wed, 30 Aug 2023 11:56:55 -0700 Subject: [PATCH] Add new notification system to add notifications from anywhere --- console/client/src/layout/IDELayout.tsx | 229 +++++++++--------- console/client/src/layout/Notification.tsx | 153 ++++++++---- console/client/src/providers/AppProviders.tsx | 5 +- .../src/providers/notifications-provider.tsx | 71 ++++++ .../client/src/providers/schema-provider.tsx | 13 +- 5 files changed, 315 insertions(+), 156 deletions(-) create mode 100644 console/client/src/providers/notifications-provider.tsx diff --git a/console/client/src/layout/IDELayout.tsx b/console/client/src/layout/IDELayout.tsx index 0c92b74f42..e9bd25b54d 100644 --- a/console/client/src/layout/IDELayout.tsx +++ b/console/client/src/layout/IDELayout.tsx @@ -1,5 +1,4 @@ import {Tab} from '@headlessui/react' -import {InformationCircleIcon} from '@heroicons/react/20/solid' import {XMarkIcon} from '@heroicons/react/24/outline' import React from 'react' import {useSearchParams} from 'react-router-dom' @@ -9,6 +8,10 @@ import {Timeline} from '../features/timeline/Timeline' import {VerbTab} from '../features/verbs/VerbTab' import {useClient} from '../hooks/use-client' import {ConsoleService} from '../protos/xyz/block/ftl/v1/console/console_connect' +import { + NotificationType, + NotificationsContext, +} from '../providers/notifications-provider' import { TabSearchParams, TabType, @@ -32,9 +35,9 @@ const unselectedTabStyle = `text-gray-300 bg-slate-100 dark:bg-slate-600` export function IDELayout() { const client = useClient(ConsoleService) const {tabs, activeTab, setActiveTab, setTabs} = React.useContext(TabsContext) + const {showNotification} = React.useContext(NotificationsContext) const [searchParams, setSearchParams] = useSearchParams() const [activeIndex, setActiveIndex] = React.useState(0) - const [invalidTabMessage, setInvalidTabMessage] = React.useState() const id = searchParams.get(TabSearchParams.id) as string const type = searchParams.get(TabSearchParams.type) as string // Set active tab index whenever our activeTab context changes @@ -76,7 +79,11 @@ export function IDELayout() { setActiveTab({id: timelineTab.id, type: timelineTab.type}) // On intial mount we have no query params set for tabs so we want to skip setting invalidTabMessage if (type === null && id === null) return - return setInvalidTabMessage(msg) + return showNotification({ + title: 'Invalid Tab', + message: msg, + type: NotificationType.Error, + }) } const inTabsList = tabs.some( ({id: tabId, type: tabType}) => tabId === id && tabType === type @@ -104,128 +111,128 @@ export function IDELayout() { return setTabs(nextTabs) } if (moduleExist && !verbExist) { - setInvalidTabMessage(`Verb ${verbId} does not exist on ${moduleId}`) + return showNotification({ + title: 'Verb not found', + message: `Verb '${verbId}' does not exist on module '${moduleId}'`, + type: NotificationType.Error, + }) } if (!moduleExist) { - setInvalidTabMessage(`Module ${moduleId} does not exist`) + return showNotification({ + title: 'Module not found', + message: `Module '${moduleId}' does not exist`, + type: NotificationType.Error, + }) } - setActiveTab({id: timelineTab.id, type: timelineTab.type}) } void validateTabs() }, [id, type]) return ( -
- + <> +
+ -
- {/* Left Column */} - - {/* Main Content */} -
-
-
-
- -
- - {tabs.map(({label, id}, i) => { - return ( - - +
+
+
+ +
+ + {tabs.map(({label, id}, i) => { + return ( + - {label} - - {i !== 0 && ( - - )} - - ) - })} - -
-
-
- - {tabs.map(({id}, i) => { - return i === 0 ? ( - - - - ) : ( - - - - ) - })} - -
-
+ {label} + + {i !== 0 && ( + + )} + + ) + })} + +
+
+
+ + {tabs.map(({id}, i) => { + return i === 0 ? ( + + + + ) : ( + + + + ) + })} + +
+ +
+
- -
-
-
+ + +
- {invalidTabMessage && ( - - } - title='Alert!' - message={invalidTabMessage} - /> - )} -
+ + ) } diff --git a/console/client/src/layout/Notification.tsx b/console/client/src/layout/Notification.tsx index 7355311e59..8a9e8267ea 100644 --- a/console/client/src/layout/Notification.tsx +++ b/console/client/src/layout/Notification.tsx @@ -1,48 +1,119 @@ -import React from 'react' import {Transition} from '@headlessui/react' -import {Portal} from '@headlessui/react' +import {XMarkIcon} from '@heroicons/react/20/solid' +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + InformationCircleIcon, +} from '@heroicons/react/24/outline' +import React, {Fragment} from 'react' +import { + NotificationType, + NotificationsContext, +} from '../providers/notifications-provider' -export const Notification: React.FC<{ - title: string - message: string - icon?: React.ReactNode - color: string -}> = ({title, message, icon, color}) => { - const [active, setActive] = React.useState(false) - React.useEffect(() => { - const timer = setTimeout(() => { - setActive(true) - }, 50) +export const Notification: React.FC = () => { + const {isOpen, notification, closeNotification} = + React.useContext(NotificationsContext) + + const iconColor = () => { + switch (notification?.type) { + case NotificationType.Success: + return 'text-green-400' + case NotificationType.Error: + return 'text-red-400' + case NotificationType.Warning: + return 'text-yellow-400' + case NotificationType.Info: + return 'text-blue-400' + default: + return 'text-gray-400' + } + } + + const icon = () => { + switch (notification?.type) { + case NotificationType.Success: + return ( +