From d5061d6fb1aeed76724959602f269f4d9bd5fe87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:18:03 +0000 Subject: [PATCH 1/4] Initial plan From 300d8e434b82c1272d971e01669d5e1a56404be8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:22:26 +0000 Subject: [PATCH 2/4] Address PR review comments: fix responsive width, security, error handling, and code quality Co-authored-by: Avdhesh-Varshney <114330097+Avdhesh-Varshney@users.noreply.github.com> --- .../modules/openai/v1/hooks/index.ts | 23 ++++++++++++++++--- .../integrations/modules/openai/v1/index.tsx | 4 ++-- .../modules/integrations/v1/index.tsx | 2 +- client/src/modules/settings/routes/index.tsx | 5 +--- .../src/modules/settings/v1/typings/index.ts | 1 - 5 files changed, 24 insertions(+), 11 deletions(-) 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..8dcb27ef 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..d76c8c1e 100644 --- a/client/src/modules/settings/modules/integrations/v1/index.tsx +++ b/client/src/modules/settings/modules/integrations/v1/index.tsx @@ -216,7 +216,7 @@ const Integrations = () => { fontSize: 14, fontWeight: 400, color: 'text.secondary', - pl: 'calc(20px + 12px)', + pl: 4, }, }} /> 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/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; From f98a970c1ccc4ee715411e79dcaf68888ed3cf7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:25:02 +0000 Subject: [PATCH 3/4] Extract duplicate sidebar code into reusable SidebarItem component Co-authored-by: Avdhesh-Varshney <114330097+Avdhesh-Varshney@users.noreply.github.com> --- .../modules/openai/v1/hooks/index.ts | 2 +- .../modules/integrations/v1/index.tsx | 148 ++------------- .../settings/v1/components/settings-tab.tsx | 159 ++-------------- .../settings/v1/components/sidebar-item.tsx | 173 ++++++++++++++++++ 4 files changed, 207 insertions(+), 275 deletions(-) create mode 100644 client/src/modules/settings/v1/components/sidebar-item.tsx 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 8dcb27ef..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,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { useNotifications } from '../../../../../../../shared/hooks/use-notification'; +import { useNotifications } from '../../../../../../../../shared/hooks/use-notification'; const useOpenAIIntegrationV1 = () => { const [apiKey, setApiKey] = useState(''); diff --git a/client/src/modules/settings/modules/integrations/v1/index.tsx b/client/src/modules/settings/modules/integrations/v1/index.tsx index d76c8c1e..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/v1/components/settings-tab.tsx b/client/src/modules/settings/v1/components/settings-tab.tsx index 024c7a84..eb8419eb 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,27 @@ 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 !== ''); + 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; From 31590898c503cdba95a214e0bdff1925ece15108 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:24:40 +0000 Subject: [PATCH 4/4] Remove active state highlighting from settings sidebar items Co-authored-by: Avdhesh-Varshney <114330097+Avdhesh-Varshney@users.noreply.github.com> --- client/src/modules/settings/v1/components/settings-tab.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/modules/settings/v1/components/settings-tab.tsx b/client/src/modules/settings/v1/components/settings-tab.tsx index eb8419eb..d9be0a8e 100644 --- a/client/src/modules/settings/v1/components/settings-tab.tsx +++ b/client/src/modules/settings/v1/components/settings-tab.tsx @@ -14,7 +14,8 @@ const SettingsTab = ({ const { name, description, icon, path, locked, isNew, newText, feature, id } = setting; const absolutePath = `${ROUTES_V1.SETTINGS}${path}`; - const isTabActive = window.location.pathname.includes(absolutePath); + // Don't highlight settings items when viewing their detail pages + const isTabActive = false; const isLastItem = index === filteredSettings.length - 1; return (