diff --git a/client/src/modules/settings/modules/integrations/modules/openai/v1/hooks/index.ts b/client/src/modules/settings/modules/integrations/modules/openai/v1/hooks/index.ts index a5e690b6..e0a4deb1 100644 --- a/client/src/modules/settings/modules/integrations/modules/openai/v1/hooks/index.ts +++ b/client/src/modules/settings/modules/integrations/modules/openai/v1/hooks/index.ts @@ -1,22 +1,31 @@ import { useState, useCallback } from 'react'; +import { useNotifications } from '../../../../../../../../shared/hooks/use-notification'; const useOpenAIIntegrationV1 = () => { const [apiKey, setApiKey] = useState(''); const [isConnected, setIsConnected] = useState(false); const [loading, setLoading] = useState(false); + const { addNotification } = useNotifications(); const handleConnect = useCallback(async () => { setLoading(true); try { // TODO: Implement the logic to connect to OpenAI setIsConnected(true); - setApiKey(apiKey); + addNotification({ + message: 'Successfully connected to OpenAI', + type: 'success', + }); } catch (error) { console.error(error); + addNotification({ + message: 'Failed to connect to OpenAI. Please try again.', + type: 'error', + }); } finally { setLoading(false); } - }, [apiKey]); + }, [addNotification]); const handleDisconnect = useCallback(async () => { setLoading(true); @@ -24,12 +33,20 @@ const useOpenAIIntegrationV1 = () => { // TODO: Implement the logic to disconnect from OpenAI setIsConnected(false); setApiKey(''); + addNotification({ + message: 'Successfully disconnected from OpenAI', + type: 'success', + }); } catch (error) { console.error(error); + addNotification({ + message: 'Failed to disconnect from OpenAI. Please try again.', + type: 'error', + }); } finally { setLoading(false); } - }, []); + }, [addNotification]); return { apiKey, diff --git a/client/src/modules/settings/modules/integrations/modules/openai/v1/index.tsx b/client/src/modules/settings/modules/integrations/modules/openai/v1/index.tsx index d834fcb6..bf1ca1ed 100644 --- a/client/src/modules/settings/modules/integrations/modules/openai/v1/index.tsx +++ b/client/src/modules/settings/modules/integrations/modules/openai/v1/index.tsx @@ -38,7 +38,7 @@ const OpenAI = () => { id="openai-api-key" name="apiKey" type={isConnected ? 'text' : 'password'} - value={isConnected ? '*'.repeat(apiKey.length) : apiKey} + value={isConnected ? '••••••••••••••••' : apiKey} placeholder={ isConnected ? 'Your API key is connected and encrypted' @@ -49,7 +49,7 @@ const OpenAI = () => { autoComplete="off" autoFocus={!isConnected && !loading} sx={{ - width: '50%', + width: { xs: '100%', md: '50%' }, '& .MuiOutlinedInput-root': { bgcolor: 'background.paper', }, diff --git a/client/src/modules/settings/modules/integrations/v1/index.tsx b/client/src/modules/settings/modules/integrations/v1/index.tsx index 41416b16..fd9c6de0 100644 --- a/client/src/modules/settings/modules/integrations/v1/index.tsx +++ b/client/src/modules/settings/modules/integrations/v1/index.tsx @@ -1,10 +1,7 @@ -import { Box, ButtonBase, useTheme } from '@mui/material'; +import { Box, useTheme } from '@mui/material'; import { Routes } from 'react-router-dom'; -import LockIcon from '@mui/icons-material/Lock'; import { SETTINGS_SIDEBAR_WIDTH } from '../../../v1/constants'; import useIntegrationsSettingsV1 from './hooks'; -import A2ZTypography from '../../../../../shared/components/atoms/typography'; -import { useCustomNavigate } from '../../../../../shared/hooks/use-custom-navigate'; import { useDevice } from '../../../../../shared/hooks/use-device'; import { ROUTES_SETTINGS_V1, @@ -12,11 +9,11 @@ import { } from '../../../../app/routes/constants/routes'; import IntegrationsHeader from './components/integrations-header'; import { IntegrationSettingType } from '../../../v1/typings'; +import SidebarItem from '../../../v1/components/sidebar-item'; const Integrations = () => { const theme = useTheme(); const { isMobileOrTablet } = useDevice(); - const navigate = useCustomNavigate(); const { integrations, routes, integrationId, activeIntegration } = useIntegrationsSettingsV1(); @@ -90,138 +87,25 @@ const Integrations = () => { > {integrations.map( (integration: IntegrationSettingType, index: number) => { - const { - id, - integrationSlug, - name, - icon, - description, - locked = false, - } = integration; + const { id, integrationSlug, name, icon, description, locked } = + integration; const absolutePath = `${ROUTES_V1.SETTINGS}${ROUTES_SETTINGS_V1.INTEGRATIONS}/${integrationSlug}`; const isTabActive = integrationId === id; + const isLastItem = index === integrations.length - 1; return ( - + { - if (locked) { - return; - } - navigate({ pathname: absolutePath }); - }} - > - - - - {icon} - {locked && ( - - )} - - - - - {description && ( - - )} - + id={id} + name={name} + description={description} + icon={icon} + path={absolutePath} + locked={locked} + isActive={isTabActive} + isLastItem={isLastItem} + testId={`integration-${name}`} + /> ); } )} diff --git a/client/src/modules/settings/routes/index.tsx b/client/src/modules/settings/routes/index.tsx index a1dd887f..ec73ff59 100644 --- a/client/src/modules/settings/routes/index.tsx +++ b/client/src/modules/settings/routes/index.tsx @@ -34,7 +34,6 @@ export const settingsRoutes = ({ path: ROUTES_SETTINGS_V1.INTEGRATIONS, name: 'Integrations', description: 'Manage your integrations', - locked: false, }, ]; @@ -85,12 +84,10 @@ export const integrationsRoutes = ({ const integrations: IntegrationSettingType[] = [ { id: 'openai', - type: 'integration', integrationSlug: 'openai', - icon: , + icon: , name: 'OpenAI', description: 'Setup your OpenAI integration', - locked: false, }, ]; diff --git a/client/src/modules/settings/v1/components/settings-tab.tsx b/client/src/modules/settings/v1/components/settings-tab.tsx index 024c7a84..d9be0a8e 100644 --- a/client/src/modules/settings/v1/components/settings-tab.tsx +++ b/client/src/modules/settings/v1/components/settings-tab.tsx @@ -1,9 +1,6 @@ -import { Box, ButtonBase, useTheme } from '@mui/material'; -import LockIcon from '@mui/icons-material/Lock'; import { ROUTES_V1 } from '../../../app/routes/constants/routes'; import { SettingTabType } from '../typings'; -import A2ZTypography from '../../../../shared/components/atoms/typography'; -import { useCustomNavigate } from '../../../../shared/hooks/use-custom-navigate'; +import SidebarItem from './sidebar-item'; const SettingsTab = ({ setting, @@ -14,149 +11,28 @@ const SettingsTab = ({ index: number; filteredSettings: SettingTabType[]; }) => { - const theme = useTheme(); - const navigate = useCustomNavigate(); - const { name, description, icon, path, locked, isNew, newText, feature } = + const { name, description, icon, path, locked, isNew, newText, feature, id } = setting; const absolutePath = `${ROUTES_V1.SETTINGS}${path}`; - const isTabActive = window.location.pathname.includes(absolutePath); - const isLocked = locked || (feature !== undefined && feature !== ''); + // Don't highlight settings items when viewing their detail pages + const isTabActive = false; + const isLastItem = index === filteredSettings.length - 1; return ( - - key={index} - component="div" - sx={{ - width: '100%', - p: 2, - display: 'flex', - flexDirection: 'column', - justifyContent: 'flex-start', - alignItems: 'flex-start', - rowGap: 0.25, - borderBottom: - index < filteredSettings.length - 1 - ? `1px solid ${theme.palette.divider}` - : 'none', - bgcolor: isTabActive - ? theme.palette.mode === 'dark' - ? 'rgba(59, 130, 246, 0.1)' - : 'rgba(240, 245, 240, 1)' - : 'transparent', - boxShadow: 'none', - outline: 'none', - transition: 'background-color 200ms ease-in-out', - '&:hover': { - bgcolor: isTabActive - ? theme.palette.mode === 'dark' - ? 'rgba(59, 130, 246, 0.15)' - : 'rgba(240, 245, 240, 0.68)' - : 'action.hover', - boxShadow: 'none', - outline: 'none', - }, - }} - onClick={() => { - if (locked) { - return; - } - navigate({ pathname: absolutePath }); - }} - > - - - - {icon} - {isLocked && ( - - )} - - - - {isNew && ( - - {newText ?? 'NEW'} - - )} - - {description && ( - - )} - + ); }; diff --git a/client/src/modules/settings/v1/components/sidebar-item.tsx b/client/src/modules/settings/v1/components/sidebar-item.tsx new file mode 100644 index 00000000..a819e9a4 --- /dev/null +++ b/client/src/modules/settings/v1/components/sidebar-item.tsx @@ -0,0 +1,173 @@ +import { Box, ButtonBase, useTheme } from '@mui/material'; +import LockIcon from '@mui/icons-material/Lock'; +import A2ZTypography from '../../../../shared/components/atoms/typography'; +import { useCustomNavigate } from '../../../../shared/hooks/use-custom-navigate'; + +interface SidebarItemProps { + id: string; + name: string; + description?: string; + icon: React.ReactNode; + path: string; + locked?: boolean; + isNew?: boolean; + newText?: string; + feature?: string; + isActive: boolean; + isLastItem: boolean; + testId?: string; +} + +const SidebarItem = ({ + name, + description, + icon, + path, + locked = false, + isNew = false, + newText, + feature, + isActive, + isLastItem, + testId, +}: SidebarItemProps) => { + const theme = useTheme(); + const navigate = useCustomNavigate(); + const isLocked = locked || (feature !== undefined && feature !== ''); + + return ( + + component="div" + sx={{ + width: '100%', + p: 2, + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'flex-start', + rowGap: 0.25, + borderBottom: isLastItem ? 'none' : `1px solid ${theme.palette.divider}`, + bgcolor: isActive + ? theme.palette.mode === 'dark' + ? 'rgba(59, 130, 246, 0.1)' + : 'rgba(240, 245, 240, 1)' + : 'transparent', + boxShadow: 'none', + outline: 'none', + transition: 'background-color 200ms ease-in-out', + '&:hover': { + bgcolor: isActive + ? theme.palette.mode === 'dark' + ? 'rgba(59, 130, 246, 0.15)' + : 'rgba(240, 245, 240, 0.68)' + : 'action.hover', + boxShadow: 'none', + outline: 'none', + }, + }} + disabled={isLocked} + onClick={() => { + if (isLocked) { + return; + } + navigate({ pathname: path }); + }} + > + + + + {icon} + {isLocked && ( + + )} + + + + {isNew && ( + + {newText ?? 'NEW'} + + )} + + {description && ( + + )} + + ); +}; + +export default SidebarItem; diff --git a/client/src/modules/settings/v1/typings/index.ts b/client/src/modules/settings/v1/typings/index.ts index 64ee2464..2bef09a6 100644 --- a/client/src/modules/settings/v1/typings/index.ts +++ b/client/src/modules/settings/v1/typings/index.ts @@ -15,7 +15,6 @@ export interface SettingTabType { export interface IntegrationSettingType { id: string; - type: string; integrationSlug: string; icon: ReactNode; name: string;