diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx
index f6199d629..4176f1cc1 100644
--- a/src/AppRoutes.tsx
+++ b/src/AppRoutes.tsx
@@ -6,49 +6,65 @@ import Home from './pages/Home';
import PublicRoute from './components/PublicRoute';
import ProtectedRoute from './components/ProtectedRoute';
import DefaultRedirect from './components/DefaultRedirect';
+import SettingsModal from './components/settings/SettingsModal';
const AppRoutes = () => {
return (
-
- {/* Public routes - redirect to /home or /onboarding if logged in */}
-
-
-
- }
- />
-
-
-
- }
- />
+ <>
+
+ {/* Public routes - redirect to /home or /onboarding if logged in */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Protected routes */}
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Protected routes */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Default redirect based on auth status */}
- } />
-
+ {/* Settings modal routes - protected */}
+
+
+
+ }
+ />
+
+ {/* Default redirect based on auth status */}
+ } />
+
+
+ {/* Settings Modal - renders over existing content when on settings routes */}
+
+ >
);
};
diff --git a/src/components/settings/SettingsHome.tsx b/src/components/settings/SettingsHome.tsx
new file mode 100644
index 000000000..2d8b4abea
--- /dev/null
+++ b/src/components/settings/SettingsHome.tsx
@@ -0,0 +1,125 @@
+import { useAppDispatch } from '../../store/hooks';
+import { clearToken } from '../../store/authSlice';
+import { useSettingsNavigation } from './hooks/useSettingsNavigation';
+import SettingsHeader from './components/SettingsHeader';
+import SettingsMenuItem from './components/SettingsMenuItem';
+
+const SettingsHome = () => {
+ const dispatch = useAppDispatch();
+ const { navigateToSettings, closeSettings } = useSettingsNavigation();
+
+ const handleLogout = async () => {
+ await dispatch(clearToken());
+ closeSettings();
+ };
+
+ const menuItems = [
+ {
+ id: 'connections',
+ title: 'Connections',
+ description: 'Manage your connected accounts and services',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('connections')
+ },
+ {
+ id: 'messaging',
+ title: 'Messaging',
+ description: 'Configure messaging preferences and templates',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('messaging')
+ },
+ {
+ id: 'privacy',
+ title: 'Privacy & Security',
+ description: 'Control your privacy and security settings',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('privacy')
+ },
+ {
+ id: 'profile',
+ title: 'Profile',
+ description: 'Update your profile information and preferences',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('profile')
+ },
+ {
+ id: 'advanced',
+ title: 'Advanced',
+ description: 'Advanced configuration and developer options',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('advanced')
+ },
+ {
+ id: 'billing',
+ title: 'Billing',
+ description: 'Manage your subscription and payment methods',
+ icon: (
+
+ ),
+ onClick: () => navigateToSettings('billing')
+ }
+ ];
+
+ return (
+
+
+
+
+ {/* Menu items */}
+
+ {menuItems.map((item, index) => (
+
+ ))}
+
+ {/* Separator before logout */}
+
+
+ {/* Logout */}
+
+
+
+ }
+ title="Log out"
+ description="Sign out of your account"
+ onClick={handleLogout}
+ dangerous
+ />
+
+
+
+ );
+};
+
+export default SettingsHome;
\ No newline at end of file
diff --git a/src/components/settings/SettingsLayout.tsx b/src/components/settings/SettingsLayout.tsx
new file mode 100644
index 000000000..aa47ab5de
--- /dev/null
+++ b/src/components/settings/SettingsLayout.tsx
@@ -0,0 +1,75 @@
+import { ReactNode, useEffect, useRef } from 'react';
+import { createPortal } from 'react-dom';
+
+interface SettingsLayoutProps {
+ children: ReactNode;
+ onClose: () => void;
+}
+
+const SettingsLayout = ({ children, onClose }: SettingsLayoutProps) => {
+ const modalRef = useRef(null);
+
+ // Handle escape key
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ onClose();
+ }
+ };
+
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, [onClose]);
+
+ // Handle backdrop click
+ const handleBackdropClick = (e: React.MouseEvent) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ // Focus management for accessibility
+ useEffect(() => {
+ const previousFocus = document.activeElement as HTMLElement;
+
+ // Focus the modal container
+ if (modalRef.current) {
+ modalRef.current.focus();
+ }
+
+ // Restore focus when modal closes
+ return () => {
+ if (previousFocus && previousFocus.focus) {
+ previousFocus.focus();
+ }
+ };
+ }, []);
+
+ const modalContent = (
+
+
e.stopPropagation()}
+ >
+ {children}
+
+
+ );
+
+ return createPortal(modalContent, document.body);
+};
+
+export default SettingsLayout;
\ No newline at end of file
diff --git a/src/components/settings/SettingsModal.tsx b/src/components/settings/SettingsModal.tsx
new file mode 100644
index 000000000..fefc5fb34
--- /dev/null
+++ b/src/components/settings/SettingsModal.tsx
@@ -0,0 +1,36 @@
+import { Routes, Route } from 'react-router-dom';
+import { useLocation } from 'react-router-dom';
+import SettingsLayout from './SettingsLayout';
+import SettingsHome from './SettingsHome';
+import ConnectionsPanel from './panels/ConnectionsPanel';
+import { useSettingsNavigation } from './hooks/useSettingsNavigation';
+
+const SettingsModal = () => {
+ const location = useLocation();
+ const { closeSettings } = useSettingsNavigation();
+
+ // Only render modal when on settings routes
+ const isSettingsRoute = location.pathname.startsWith('/settings');
+
+ if (!isSettingsRoute) {
+ return null;
+ }
+
+ return (
+
+
+ } />
+ } />
+
+ {/* Future settings panels */}
+ Messaging settings coming soon} />
+ Privacy settings coming soon} />
+ Profile settings coming soon} />
+ Advanced settings coming soon} />
+ Billing settings coming soon} />
+
+
+ );
+};
+
+export default SettingsModal;
\ No newline at end of file
diff --git a/src/components/settings/components/SettingsBackButton.tsx b/src/components/settings/components/SettingsBackButton.tsx
new file mode 100644
index 000000000..a970bd845
--- /dev/null
+++ b/src/components/settings/components/SettingsBackButton.tsx
@@ -0,0 +1,38 @@
+interface SettingsBackButtonProps {
+ onClick: () => void;
+ title?: string;
+ className?: string;
+}
+
+const SettingsBackButton = ({
+ onClick,
+ title = 'Settings',
+ className = ''
+}: SettingsBackButtonProps) => {
+ return (
+
+ );
+};
+
+export default SettingsBackButton;
\ No newline at end of file
diff --git a/src/components/settings/components/SettingsHeader.tsx b/src/components/settings/components/SettingsHeader.tsx
new file mode 100644
index 000000000..4887be2b3
--- /dev/null
+++ b/src/components/settings/components/SettingsHeader.tsx
@@ -0,0 +1,46 @@
+import { useAppSelector } from '../../../store/hooks';
+
+interface SettingsHeaderProps {
+ className?: string;
+}
+
+const SettingsHeader = ({ className = '' }: SettingsHeaderProps) => {
+ const user = useAppSelector((state) => state.user.user);
+
+ // Get user initials for avatar
+ const getInitials = (firstName?: string, lastName?: string): string => {
+ if (!firstName && !lastName) return 'U';
+ const first = firstName?.charAt(0) || '';
+ const last = lastName?.charAt(0) || '';
+ return (first + last).toUpperCase();
+ };
+
+ const initials = getInitials(user?.firstName, user?.lastName);
+ const displayName = user?.firstName && user?.lastName
+ ? `${user.firstName} ${user.lastName}`
+ : user?.firstName || user?.username || 'User';
+ const displayUsername = user?.username ? `@${user.username}` : '';
+
+ return (
+
+
+ {/* Avatar */}
+
+ {initials}
+
+
+ {/* User info */}
+
+
+ {displayName}
+
+ {displayUsername && (
+
{displayUsername}
+ )}
+
+
+
+ );
+};
+
+export default SettingsHeader;
\ No newline at end of file
diff --git a/src/components/settings/components/SettingsMenuItem.tsx b/src/components/settings/components/SettingsMenuItem.tsx
new file mode 100644
index 000000000..68f148804
--- /dev/null
+++ b/src/components/settings/components/SettingsMenuItem.tsx
@@ -0,0 +1,64 @@
+import { ReactNode } from 'react';
+
+interface SettingsMenuItemProps {
+ icon: ReactNode;
+ title: string;
+ description?: string;
+ onClick: () => void;
+ dangerous?: boolean;
+ className?: string;
+}
+
+const SettingsMenuItem = ({
+ icon,
+ title,
+ description,
+ onClick,
+ dangerous = false,
+ className = ''
+}: SettingsMenuItemProps) => {
+ const textColor = dangerous ? 'text-red-600' : 'text-gray-900';
+ const descriptionColor = dangerous ? 'text-red-500/70' : 'text-gray-500';
+
+ return (
+
+ );
+};
+
+export default SettingsMenuItem;
\ No newline at end of file
diff --git a/src/components/settings/components/SettingsPanelLayout.tsx b/src/components/settings/components/SettingsPanelLayout.tsx
new file mode 100644
index 000000000..d29cf9558
--- /dev/null
+++ b/src/components/settings/components/SettingsPanelLayout.tsx
@@ -0,0 +1,27 @@
+import { ReactNode } from 'react';
+import SettingsBackButton from './SettingsBackButton';
+
+interface SettingsPanelLayoutProps {
+ title: string;
+ onBack: () => void;
+ children: ReactNode;
+ className?: string;
+}
+
+const SettingsPanelLayout = ({
+ title,
+ onBack,
+ children,
+ className = ''
+}: SettingsPanelLayoutProps) => {
+ return (
+
+ );
+};
+
+export default SettingsPanelLayout;
\ No newline at end of file
diff --git a/src/components/settings/hooks/useSettingsAnimation.ts b/src/components/settings/hooks/useSettingsAnimation.ts
new file mode 100644
index 000000000..fa960caf6
--- /dev/null
+++ b/src/components/settings/hooks/useSettingsAnimation.ts
@@ -0,0 +1,67 @@
+import { useState, useEffect } from 'react';
+
+export type AnimationState = 'entering' | 'entered' | 'exiting' | 'exited';
+
+interface SettingsAnimationHook {
+ isVisible: boolean;
+ animationState: AnimationState;
+ startEntry: () => void;
+ startExit: () => void;
+}
+
+export const useSettingsAnimation = (
+ duration = 200
+): SettingsAnimationHook => {
+ const [animationState, setAnimationState] = useState('exited');
+
+ const isVisible = animationState === 'entering' || animationState === 'entered';
+
+ const startEntry = () => {
+ setAnimationState('entering');
+ setTimeout(() => {
+ setAnimationState('entered');
+ }, duration);
+ };
+
+ const startExit = () => {
+ setAnimationState('exiting');
+ setTimeout(() => {
+ setAnimationState('exited');
+ }, duration);
+ };
+
+ return {
+ isVisible,
+ animationState,
+ startEntry,
+ startExit
+ };
+};
+
+// Hook for panel slide animations (slide from right)
+export const usePanelAnimation = (isActive: boolean, duration = 250) => {
+ const [mounted, setMounted] = useState(isActive);
+
+ useEffect(() => {
+ if (isActive) {
+ setMounted(true);
+ } else {
+ const timer = setTimeout(() => setMounted(false), duration);
+ return () => clearTimeout(timer);
+ }
+ }, [isActive, duration]);
+
+ const getPanelClasses = () => {
+ const baseClasses = 'transition-all duration-250 ease-out';
+ if (!mounted) return `${baseClasses} opacity-0`;
+
+ return isActive
+ ? `${baseClasses} opacity-100 translate-x-0`
+ : `${baseClasses} opacity-0 translate-x-full`;
+ };
+
+ return {
+ mounted,
+ panelClasses: getPanelClasses()
+ };
+};
\ No newline at end of file
diff --git a/src/components/settings/hooks/useSettingsNavigation.ts b/src/components/settings/hooks/useSettingsNavigation.ts
new file mode 100644
index 000000000..145ae9811
--- /dev/null
+++ b/src/components/settings/hooks/useSettingsNavigation.ts
@@ -0,0 +1,57 @@
+import { useNavigate, useLocation } from 'react-router-dom';
+import { useCallback } from 'react';
+
+export type SettingsRoute = 'home' | 'connections' | 'messaging' | 'privacy' | 'profile' | 'advanced' | 'billing';
+
+interface SettingsNavigationHook {
+ currentRoute: SettingsRoute;
+ navigateToSettings: (route?: SettingsRoute) => void;
+ navigateBack: () => void;
+ closeSettings: () => void;
+}
+
+export const useSettingsNavigation = (): SettingsNavigationHook => {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ // Determine current settings route from URL
+ const getCurrentRoute = (): SettingsRoute => {
+ const path = location.pathname;
+ if (path.includes('/settings/connections')) return 'connections';
+ if (path.includes('/settings/messaging')) return 'messaging';
+ if (path.includes('/settings/privacy')) return 'privacy';
+ if (path.includes('/settings/profile')) return 'profile';
+ if (path.includes('/settings/advanced')) return 'advanced';
+ if (path.includes('/settings/billing')) return 'billing';
+ return 'home';
+ };
+
+ const currentRoute = getCurrentRoute();
+
+ const navigateToSettings = useCallback((route: SettingsRoute = 'home') => {
+ if (route === 'home') {
+ navigate('/settings');
+ } else {
+ navigate(`/settings/${route}`);
+ }
+ }, [navigate]);
+
+ const navigateBack = useCallback(() => {
+ if (currentRoute === 'home') {
+ navigate('/home');
+ } else {
+ navigate('/settings');
+ }
+ }, [navigate, currentRoute]);
+
+ const closeSettings = useCallback(() => {
+ navigate('/home');
+ }, [navigate]);
+
+ return {
+ currentRoute,
+ navigateToSettings,
+ navigateBack,
+ closeSettings
+ };
+};
\ No newline at end of file
diff --git a/src/components/settings/panels/ConnectionsPanel.tsx b/src/components/settings/panels/ConnectionsPanel.tsx
new file mode 100644
index 000000000..99dc4633b
--- /dev/null
+++ b/src/components/settings/panels/ConnectionsPanel.tsx
@@ -0,0 +1,210 @@
+import { useState, useMemo } from 'react';
+import { useAppSelector } from '../../../store/hooks';
+import { selectIsAuthenticated } from '../../../store/telegramSelectors';
+import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
+import SettingsPanelLayout from '../components/SettingsPanelLayout';
+import TelegramConnectionModal from '../../TelegramConnectionModal';
+
+import BinanceIcon from '../../../assets/icons/binance.svg';
+import NotionIcon from '../../../assets/icons/notion.svg';
+import TelegramIcon from '../../../assets/icons/telegram.svg';
+import MetamaskIcon from '../../../assets/icons/metamask.svg';
+import GoogleIcon from '../../../assets/icons/GoogleIcon';
+
+interface ConnectOption {
+ id: string;
+ name: string;
+ description: string;
+ icon: React.ReactElement;
+ comingSoon?: boolean;
+}
+
+// Reused from ConnectStep.tsx - helper to check saved session
+const hasSavedSession = (): boolean => {
+ try {
+ return !!localStorage.getItem('telegram_session');
+ } catch {
+ return false;
+ }
+};
+
+const ConnectionsPanel = () => {
+ const { navigateBack } = useSettingsNavigation();
+ const [isTelegramModalOpen, setIsTelegramModalOpen] = useState(false);
+
+ // Redux state
+ const isTelegramAuthenticated = useAppSelector(selectIsAuthenticated);
+ const sessionString = useAppSelector((state) => state.telegram.sessionString);
+
+ // Check if Telegram account is connected (authenticated or has saved session)
+ const isTelegramConnected = useMemo(() => {
+ return isTelegramAuthenticated || !!sessionString || hasSavedSession();
+ }, [isTelegramAuthenticated, sessionString]);
+
+ // Connection options - reused from ConnectStep.tsx
+ const connectOptions: ConnectOption[] = [
+ {
+ id: 'telegram',
+ name: 'Telegram',
+ description: 'Organize chats, automate messages and get insights.',
+ icon:
,
+ },
+ {
+ id: 'google',
+ name: 'Google',
+ description: 'Manage emails, contacts and calendar events',
+ icon: ,
+ comingSoon: true,
+ },
+ {
+ id: 'notion',
+ name: 'Notion',
+ description: 'Manage tasks, documents and everything else in your Notion',
+ icon:
,
+ comingSoon: true,
+ },
+ {
+ id: 'wallet',
+ name: 'Web3 Wallet',
+ description: 'Trade the trenches in a safe and secure way.',
+ icon:
,
+ comingSoon: true,
+ },
+ {
+ id: 'exchange',
+ name: 'Crypto Trading Exchanges',
+ description: 'Connect and make trades with deep insights.',
+ icon:
,
+ comingSoon: true,
+ },
+ ];
+
+ // Check if an account is connected
+ const isAccountConnected = (accountId: string): boolean => {
+ if (accountId === 'telegram') {
+ return isTelegramConnected;
+ }
+ // Add other account checks here when implemented
+ return false;
+ };
+
+ const handleConnect = (provider: string) => {
+ if (provider === 'telegram') {
+ if (isTelegramConnected) {
+ // TODO: Show disconnect confirmation
+ console.log('Disconnect Telegram');
+ } else {
+ setIsTelegramModalOpen(true);
+ }
+ return;
+ }
+
+ if (connectOptions.find(opt => opt.id === provider)?.comingSoon) {
+ console.log(`${provider} coming soon`);
+ return;
+ }
+
+ console.log(`Connecting to ${provider}`);
+ };
+
+ const handleTelegramComplete = () => {
+ setIsTelegramModalOpen(false);
+ };
+
+ const getConnectionStatus = (optionId: string) => {
+ const isConnected = isAccountConnected(optionId);
+ if (isConnected) {
+ return { label: 'Connected', className: 'bg-green-100 text-green-700 border-green-200' };
+ }
+ const option = connectOptions.find(opt => opt.id === optionId);
+ if (option?.comingSoon) {
+ return { label: 'Coming Soon', className: 'bg-gray-100 text-gray-600 border-gray-200' };
+ }
+ return { label: 'Not Connected', className: 'bg-red-50 text-red-600 border-red-200' };
+ };
+
+ return (
+ <>
+
+
+
+
Connected Services
+
+ Manage your connected accounts and services. Connect more accounts for better AI intelligence.
+
+
+
+
+ {connectOptions.map((option) => {
+ const isConnected = isAccountConnected(option.id);
+ const status = getConnectionStatus(option.id);
+
+ return (
+
+
+
+ {option.icon}
+
+
+
{option.name}
+
{option.description}
+
+
+
+
+ {/* Status badge */}
+
+ {status.label}
+
+
+ {/* Action button */}
+ {!option.comingSoon && (
+
+ )}
+
+
+ );
+ })}
+
+
+ {/* Security notice */}
+
+
+
+
+
🔒 Privacy & Security
+
+ All data and credentials are stored locally with zero-data retention policy.
+ Your information is encrypted and never shared with third parties.
+
+
+
+
+
+
+
+ {/* Telegram Connection Modal */}
+ setIsTelegramModalOpen(false)}
+ onComplete={handleTelegramComplete}
+ />
+ >
+ );
+};
+
+export default ConnectionsPanel;
\ No newline at end of file
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 95e767d8a..f74a2a29e 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -36,8 +36,7 @@ const Home = () => {
};
const handleManageConnections = () => {
- // TODO: Navigate to connections management page
- console.log('Manage connections');
+ navigate('/settings/connections');
};
const handleDeleteAllData = () => {