Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions client/src/modules/app/routes/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ export enum ROUTES_HOME_V1 {

export enum ROUTES_SETTINGS_V1 {
PROFILE = '/profile',
INTEGRATIONS = '/integrations',
}

export enum ROUTES_SETTINGS_INTEGRATIONS_V1 {
OPENAI = '/openai',
}
6 changes: 6 additions & 0 deletions client/src/modules/settings/modules/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { lazy } from 'react';

export const ProfileLazyComponentV1 = lazy(() => import('./profile/v1'));
export const IntegrationsLazyComponentV1 = lazy(
() => import('./integrations/v1')
);
export const OpenAILazyComponentV1 = lazy(
() => import('./integrations/modules/openai/v1')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useState, useCallback } from 'react';
import { useNotifications } from '../../../../../../../../shared/hooks/use-notification';

const useOpenAIIntegrationV1 = () => {
const { addNotification } = useNotifications();

const [apiKey, setApiKey] = useState<string>('');
const [isConnected, setIsConnected] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);

const handleConnect = useCallback(async () => {
setLoading(true);
try {
// TODO: Implement the logic to connect to OpenAI
setIsConnected(true);
addNotification({
message: 'OpenAI connected successfully',
type: 'success',
});
} catch (error) {
console.error(error);
addNotification({
message: 'Failed to connect to OpenAI',
type: 'error',
});
} finally {
setLoading(false);
}
}, [addNotification]);

const handleDisconnect = useCallback(async () => {
setLoading(true);
try {
// TODO: Implement the logic to disconnect from OpenAI
setIsConnected(false);
setApiKey('');
addNotification({
message: 'OpenAI disconnected successfully',
type: 'success',
});
} catch (error) {
console.error(error);
addNotification({
message: 'Failed to disconnect from OpenAI',
type: 'error',
});
} finally {
setLoading(false);
}
}, [addNotification]);

return {
apiKey,
setApiKey,
isConnected,
loading,
handleConnect,
handleDisconnect,
};
};

export default useOpenAIIntegrationV1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Box, Button, Stack, CircularProgress } from '@mui/material';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import InputBox from '../../../../../../../shared/components/atoms/input-box';
import A2ZTypography from '../../../../../../../shared/components/atoms/typography';
import useOpenAIIntegrationV1 from './hooks';

const OpenAI = () => {
const {
apiKey,
setApiKey,
isConnected,
loading,
handleConnect,
handleDisconnect,
} = useOpenAIIntegrationV1();

return (
<Box
sx={{
p: { xs: 2, md: 3 },
height: '100%',
overflow: 'auto',
}}
>
<Stack spacing={3}>
<A2ZTypography
text="Connect your OpenAI account by providing your API key."
variant="body2"
props={{
sx: {
color: 'text.secondary',
},
}}
/>

<Box>
<InputBox
id="openai-api-key"
name="apiKey"
type={isConnected ? 'text' : 'password'}
value={isConnected ? '••••••••••••••••' : apiKey}
placeholder={
isConnected
? 'Your API key is connected and encrypted'
: 'Enter your OpenAI API key'
}
icon={<VpnKeyIcon />}
disabled={isConnected}
autoComplete="off"
autoFocus={!isConnected && !loading}
sx={{
width: { xs: '100%', md: '50%' },
'& .MuiOutlinedInput-root': {
bgcolor: 'background.paper',
},
}}
slotProps={{
htmlInput: {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
setApiKey(e.target.value);
},
},
}}
/>
<A2ZTypography
text={
isConnected
? 'Your API key is connected and encrypted'
: 'Your API key will be securely stored and encrypted'
}
variant="caption"
props={{
sx: {
mt: 0.5,
display: 'block',
color: 'text.secondary',
},
}}
/>
</Box>

<Box>
<Button
variant="contained"
color={isConnected ? 'error' : 'primary'}
disabled={loading || !apiKey.trim()}
onClick={isConnected ? handleDisconnect : handleConnect}
sx={{
py: 1.2,
fontSize: '1rem',
borderRadius: 1,
}}
>
{loading ? (
<>
<CircularProgress size={18} sx={{ mr: 1 }} />
{isConnected ? 'Disconnecting...' : 'Connecting...'}
</>
) : isConnected ? (
'Disconnect'
) : (
'Connect'
)}
</Button>
</Box>
</Stack>
</Box>
);
};

export default OpenAI;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Box, IconButton, useTheme } from '@mui/material';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import A2ZTypography from '../../../../../../shared/components/atoms/typography';
import { HEADER_HEIGHT } from '../../../../../../shared/components/organisms/header/constants';
import { useDevice } from '../../../../../../shared/hooks/use-device';
import { useCustomNavigate } from '../../../../../../shared/hooks/use-custom-navigate';
import {
ROUTES_SETTINGS_V1,
ROUTES_V1,
} from '../../../../../app/routes/constants/routes';

interface IntegrationsHeaderProps {
title: string;
}

const IntegrationsHeader = ({ title }: IntegrationsHeaderProps) => {
const theme = useTheme();
const { isMobileOrTablet } = useDevice();
const navigate = useCustomNavigate();

const handleBackClick = () => {
navigate({
pathname: `${ROUTES_V1.SETTINGS}${ROUTES_SETTINGS_V1.INTEGRATIONS}`,
});
};

return (
<Box
sx={{
height: `${HEADER_HEIGHT}px`,
minHeight: `${HEADER_HEIGHT}px`,
maxHeight: `${HEADER_HEIGHT}px`,
display: 'flex',
alignItems: 'center',
px: { xs: 2, md: 3 },
borderBottom: `1px solid ${theme.palette.divider}`,
bgcolor: 'background.paper',
gap: 2,
}}
>
{isMobileOrTablet && (
<IconButton
onClick={handleBackClick}
sx={{
color: 'text.primary',
'&:hover': {
bgcolor: 'action.hover',
},
}}
aria-label="Go back to integrations"
>
<ArrowBackIcon />
</IconButton>
)}
<A2ZTypography
text={title}
variant="h6"
props={{
sx: {
fontWeight: 600,
color: 'text.primary',
flex: 1,
},
}}
/>
</Box>
);
};

export default IntegrationsHeader;
39 changes: 39 additions & 0 deletions client/src/modules/settings/modules/integrations/v1/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { integrationsRoutes } from '../../../../routes';
import { useDevice } from '../../../../../../shared/hooks/use-device';

const useIntegrationsSettingsV1 = () => {
const location = useLocation();
const { isMobileOrTablet } = useDevice();

const { integrations, routes } = integrationsRoutes({ isMobileOrTablet });

const integrationId = useMemo(() => {
const pathParts = location.pathname.split('/');
const integrationsIndex = pathParts.findIndex(
part => part === 'integrations'
);
if (integrationsIndex !== -1 && pathParts[integrationsIndex + 1]) {
const slug = pathParts[integrationsIndex + 1];
return integrations.find(
integration => integration.integrationSlug === slug
)?.id;
}
return null;
}, [location.pathname, integrations]);

const activeIntegration = useMemo(() => {
if (!integrationId) return undefined;
return integrations.find(integration => integration.id === integrationId);
}, [integrationId, integrations]);

return {
integrations,
routes,
integrationId,
activeIntegration,
};
};

export default useIntegrationsSettingsV1;
Loading
Loading