From c104fbcaa6369fd2919751c36f4d77149d1d5fe6 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Tue, 5 Nov 2024 17:20:38 +0200 Subject: [PATCH] feat: refactor existing components in preparation for notifications --- frontend/webapp/app/page.tsx | 41 +++-- .../connection-notification.tsx | 15 +- .../destinations/add-destination/index.tsx | 2 +- .../add-rule-modal/index.tsx | 2 +- frontend/webapp/hooks/useSSE.ts | 54 +++---- .../reuseable-components/divider/index.tsx | 24 +-- .../nodes-data-flow/nodes/base-node.tsx | 2 +- .../notification-note/index.tsx | 142 +++++++++--------- .../reuseable-components/status/index.tsx | 2 +- frontend/webapp/utils/functions/icons.ts | 24 ++- 10 files changed, 139 insertions(+), 169 deletions(-) diff --git a/frontend/webapp/app/page.tsx b/frontend/webapp/app/page.tsx index ec28d1ded..ea08044df 100644 --- a/frontend/webapp/app/page.tsx +++ b/frontend/webapp/app/page.tsx @@ -1,46 +1,39 @@ 'use client'; import { useEffect } from 'react'; -import { useConfig } from '@/hooks'; import { ROUTES, CONFIG } from '@/utils'; import { useRouter } from 'next/navigation'; -import { addNotification, store } from '@/store'; +import { useConfig, useNotify } from '@/hooks'; import { Loader } from '@keyval-dev/design-system'; export default function App() { const router = useRouter(); + const notify = useNotify(); const { data, error } = useConfig(); useEffect(() => { - data && renderCurrentPage(); - }, [data, error]); - - useEffect(() => { - if (!error) return; - store.dispatch( - addNotification({ - id: '1', + if (error) { + notify({ message: 'An error occurred', title: 'Error', type: 'error', target: 'notification', crdType: 'notification', - }) - ); - router.push(ROUTES.OVERVIEW); - }, [error]); + }); - function renderCurrentPage() { - const { installation } = data; + router.push(ROUTES.OVERVIEW); + } else if (data) { + const { installation } = data; - switch (installation) { - case CONFIG.NEW: - case CONFIG.APPS_SELECTED: - router.push(ROUTES.CHOOSE_SOURCES); - break; - case CONFIG.FINISHED: - router.push(ROUTES.OVERVIEW); + switch (installation) { + case CONFIG.NEW: + case CONFIG.APPS_SELECTED: + router.push(ROUTES.CHOOSE_SOURCES); + break; + case CONFIG.FINISHED: + router.push(ROUTES.OVERVIEW); + } } - } + }, [data, error]); return ; } diff --git a/frontend/webapp/containers/main/destinations/add-destination/connect-destination-modal-body/connection-notification.tsx b/frontend/webapp/containers/main/destinations/add-destination/connect-destination-modal-body/connection-notification.tsx index 960bc3630..c94d7ef21 100644 --- a/frontend/webapp/containers/main/destinations/add-destination/connect-destination-modal-body/connection-notification.tsx +++ b/frontend/webapp/containers/main/destinations/add-destination/connect-destination-modal-body/connection-notification.tsx @@ -1,25 +1,16 @@ import { NotificationNote } from '@/reuseable-components'; import styled from 'styled-components'; -export const ConnectionNotification = ({ - showConnectionError, - destination, -}) => ( +export const ConnectionNotification = ({ showConnectionError, destination }) => ( <> {showConnectionError && ( - + )} {destination?.fields && !showConnectionError && ( - + )} diff --git a/frontend/webapp/containers/main/destinations/add-destination/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/index.tsx index 0f63dc7a9..b3356b2b3 100644 --- a/frontend/webapp/containers/main/destinations/add-destination/index.tsx +++ b/frontend/webapp/containers/main/destinations/add-destination/index.tsx @@ -80,7 +80,7 @@ export function ChooseDestinationContainer() { router.push('/choose-sources'), diff --git a/frontend/webapp/containers/main/instrumentation-rules/add-rule-modal/index.tsx b/frontend/webapp/containers/main/instrumentation-rules/add-rule-modal/index.tsx index 5c96ad3a8..6af49d440 100644 --- a/frontend/webapp/containers/main/instrumentation-rules/add-rule-modal/index.tsx +++ b/frontend/webapp/containers/main/instrumentation-rules/add-rule-modal/index.tsx @@ -70,7 +70,7 @@ export const AddRuleModal: React.FC = ({ isOpen, onClose }) => { /> Date.now() - 2000 - ) { + if (eventBuffer.current[key] && eventBuffer.current[key].id > Date.now() - 2000) { eventBuffer.current[key] = notification; return; } else { @@ -37,16 +35,13 @@ export function useSSE() { } // Dispatch the notification to the store - store.dispatch( - addNotification({ - id: eventBuffer.current[key].id, - message: eventBuffer.current[key].message, - title: eventBuffer.current[key].title, - type: eventBuffer.current[key].type, - target: eventBuffer.current[key].target, - crdType: eventBuffer.current[key].crdType, - }) - ); + notify({ + message: eventBuffer.current[key].message, + title: eventBuffer.current[key].title, + type: eventBuffer.current[key].type, + target: eventBuffer.current[key].target, + crdType: eventBuffer.current[key].crdType, + }); // Reset retry count on successful connection setRetryCount(0); @@ -60,10 +55,7 @@ export function useSSE() { setRetryCount((prevRetryCount) => { if (prevRetryCount < maxRetries) { const newRetryCount = prevRetryCount + 1; - const retryTimeout = Math.min( - 10000, - 1000 * Math.pow(2, newRetryCount) - ); + const retryTimeout = Math.min(10000, 1000 * Math.pow(2, newRetryCount)); setTimeout(() => { connect(); @@ -71,20 +63,16 @@ export function useSSE() { return newRetryCount; } else { - console.error( - 'Max retries reached. Could not reconnect to EventSource.' - ); - store.dispatch( - addNotification({ - id: Date.now().toString(), - message: - 'Connection to the server failed. Please reboot the application.', - title: 'Connection Error', - type: 'error', - target: 'system', - crdType: 'connection', - }) - ); + console.error('Max retries reached. Could not reconnect to EventSource.'); + + notify({ + message: 'Connection to the server failed. Please reboot the application.', + title: 'Connection Error', + type: 'error', + target: 'system', + crdType: 'connection', + }); + return prevRetryCount; } }); diff --git a/frontend/webapp/reuseable-components/divider/index.tsx b/frontend/webapp/reuseable-components/divider/index.tsx index 5ba268e48..26c80541b 100644 --- a/frontend/webapp/reuseable-components/divider/index.tsx +++ b/frontend/webapp/reuseable-components/divider/index.tsx @@ -9,28 +9,14 @@ interface DividerProps { } const StyledDivider = styled.div` - width: ${({ orientation, thickness }) => - orientation === 'vertical' ? `${thickness}px` : '100%'}; - height: ${({ orientation, thickness }) => - orientation === 'horizontal' ? `${thickness}px` : '100%'}; + width: ${({ orientation, thickness }) => (orientation === 'vertical' ? `${thickness}px` : '100%')}; + height: ${({ orientation, thickness }) => (orientation === 'horizontal' ? `${thickness}px` : '100%')}; background-color: ${({ color, theme }) => color || theme.colors.border}; - margin: ${({ margin }) => margin || '8px 0'}; + margin: ${({ orientation, margin }) => margin || (orientation === 'horizontal' ? '8px 0' : '0 8px')}; `; -const Divider: React.FC = ({ - thickness = 1, - color, - margin, - orientation = 'horizontal', -}) => { - return ( - - ); +const Divider: React.FC = ({ thickness = 1, color, margin, orientation = 'horizontal' }) => { + return ; }; export { Divider }; diff --git a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx index ff66266f5..39844275c 100644 --- a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx +++ b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx @@ -141,7 +141,7 @@ const BaseNode = ({ nodeWidth, isConnectable, data }: BaseNodeProps) => { {renderStatus()} - {isError ? : null} + {isError ? : null} {renderHandles()} ); diff --git a/frontend/webapp/reuseable-components/notification-note/index.tsx b/frontend/webapp/reuseable-components/notification-note/index.tsx index d61749943..0978a9cbe 100644 --- a/frontend/webapp/reuseable-components/notification-note/index.tsx +++ b/frontend/webapp/reuseable-components/notification-note/index.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import styled, { css } from 'styled-components'; import Image from 'next/image'; import { Text } from '../text'; +import { Divider } from '../divider'; +import styled from 'styled-components'; -// Define the notification types type NotificationType = 'warning' | 'error' | 'success' | 'info' | 'default'; interface NotificationProps { type: NotificationType; - text: string; + title?: string; + message?: string; action?: { label: string; onClick: () => void; @@ -16,103 +17,104 @@ interface NotificationProps { style?: React.CSSProperties; } +const getTextColor = ({ type }: { type: NotificationType }) => { + switch (type) { + case 'warning': + return '#E9CF35'; + case 'error': + return '#E25A5A'; + case 'success': + return '#81AF65'; + case 'info': + return '#B8B8B8'; + case 'default': + default: + return '#AABEF7'; + } +}; + +const getBackgroundColor = ({ type }: { type: NotificationType }) => { + switch (type) { + case 'warning': + return '#472300'; + case 'error': + return '#431919'; + case 'success': + return '#172013'; + case 'info': + return '#242424'; + case 'default': + default: + return '#181944'; + } +}; + +const getIconSource = ({ type }: { type: NotificationType }) => { + switch (type) { + case 'warning': + return '/icons/notification/warning-icon.svg'; + case 'error': + return '/icons/notification/error-icon.svg'; + case 'success': + return '/icons/notification/success-icon.svg'; + case 'info': + return '/icons/common/info.svg'; + default: + return '/brand/odigos-icon.svg'; + } +}; + const NotificationContainer = styled.div<{ type: NotificationType }>` display: flex; align-items: center; - justify-content: space-between; padding: 12px 16px; border-radius: 32px; - - background-color: ${({ type }) => { - switch (type) { - case 'warning': - return '#472300'; // Orange - case 'error': - return 'rgba(226, 90, 90, 0.12);'; - case 'success': - return '#28A745'; // Green - case 'info': - return '#F9F9F90A'; // Default to info color - case 'default': - default: - return '#181944'; // Blue - } - }}; + background-color: ${getBackgroundColor}; `; -const IconWrapper = styled.div` - margin-right: 12px; +const TextWrapper = styled.div` display: flex; - justify-content: center; align-items: center; + margin: 0 12px; + height: 12px; `; const Title = styled(Text)<{ type: NotificationType }>` font-size: 14px; - color: ${({ type }) => { - switch (type) { - case 'warning': - return '#E9CF35'; - case 'error': - return '#E25A5A'; - case 'success': - return '#28A745'; - case 'info': - return '#B8B8B8'; - case 'default': - default: - return '#AABEF7'; - } - }}; + color: ${getTextColor}; `; -const TitleWrapper = styled.div` - display: flex; - align-items: center; +const Message = styled(Text)<{ type: NotificationType }>` + font-size: 12px; + color: ${getTextColor}; `; const ActionButtonWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; + margin-left: 40px; `; const ActionButton = styled(Text)` - text-decoration: underline; text-transform: uppercase; + text-decoration: underline; font-size: 14px; - font-weight: 400; font-family: ${({ theme }) => theme.font_family.secondary}; + cursor: pointer; `; -const NotificationIcon = ({ type }: { type: NotificationType }) => { - switch (type) { - case 'warning': - return warning; - case 'error': - return error; - case 'success': - return success; - case 'info': - return info; - default: - return info; - } -}; - -const NotificationNote: React.FC = ({ type, text, action, style }) => { +const NotificationNote: React.FC = ({ type, title, message, action, style }) => { return ( - - - - - {text} - + {type} + + + {title && {title}} + {title && message && } + {message && {message}} + + {action && ( - {action.label} + {action.label} )} diff --git a/frontend/webapp/reuseable-components/status/index.tsx b/frontend/webapp/reuseable-components/status/index.tsx index 59038ae18..967d90df1 100644 --- a/frontend/webapp/reuseable-components/status/index.tsx +++ b/frontend/webapp/reuseable-components/status/index.tsx @@ -72,7 +72,7 @@ const Status: React.FC = (props) => { {withIcon && ( - status + status )} diff --git a/frontend/webapp/utils/functions/icons.ts b/frontend/webapp/utils/functions/icons.ts index 2be9175f4..5cb4ca5ac 100644 --- a/frontend/webapp/utils/functions/icons.ts +++ b/frontend/webapp/utils/functions/icons.ts @@ -2,27 +2,37 @@ import type { ActionsType, InstrumentationRuleType } from '@/types'; const BRAND_ICON = '/brand/odigos-icon.svg'; -export const getStatusIcon = (active?: boolean) => { - const path = '/icons/notification/'; +export const getStatusIcon = (status?: 'success' | 'error' | 'info') => { + if (!status) return BRAND_ICON; - return `${path}${active ? 'success-icon' : 'error-icon2'}.svg`; + switch (status) { + case 'success': + return '/icons/notification/success-icon.svg'; + + case 'error': + return '/icons/notification/error-icon2.svg'; + + case 'info': + return '/icons/common/info.svg'; + + default: + return BRAND_ICON; + } }; export const getRuleIcon = (type?: InstrumentationRuleType) => { if (!type) return BRAND_ICON; - const path = '/icons/rules/'; const typeLowerCased = type.replaceAll('-', '').toLowerCase(); - return `${path}${typeLowerCased}.svg`; + return `/icons/rules/${typeLowerCased}.svg`; }; export const getActionIcon = (type?: ActionsType | 'sampler') => { if (!type) return BRAND_ICON; - const path = '/icons/actions/'; const typeLowerCased = type.toLowerCase(); const isSampler = typeLowerCased.includes('sampler'); - return `${path}${isSampler ? 'sampler' : typeLowerCased}.svg`; + return `/icons/actions/${isSampler ? 'sampler' : typeLowerCased}.svg`; };