Refactor TelegramConnectionModal and update ConnectStep imports

- Cleaned up whitespace in TelegramConnectionModal for improved code readability.
- Updated import paths in ConnectStep to align with the new asset structure, ensuring consistency across components.
- Enhanced the structure of reducers in the Telegram state management to improve clarity and maintainability.

These changes streamline the codebase and enhance the overall organization of the Telegram integration within the application.
This commit is contained in:
Steven Enamakel
2026-01-28 06:08:04 +05:30
parent 8fcac9db07
commit ccf876b635
3 changed files with 263 additions and 207 deletions
+178 -178
View File
@@ -125,7 +125,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
}
resolve(password.trim());
};
// Set a timeout to prevent hanging forever
setTimeout(() => {
if (passwordResolverRef.current) {
@@ -139,7 +139,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
async (err) => {
// Handle errors
const errorMessage = err.message || 'Authentication error';
// Check if it's a 2FA password needed error
if (errorMessage.includes('SESSION_PASSWORD_NEEDED') || errorMessage.includes('PASSWORD')) {
// This should trigger the password callback, but if it doesn't, handle it here
@@ -178,7 +178,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
} catch (err) {
setIsAuthenticating(false);
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
// If it's a password needed error, we should already be on the 2FA step
// Don't show it as an error, just log it
if (errorMessage.includes('SESSION_PASSWORD_NEEDED')) {
@@ -190,7 +190,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
}
return; // Don't show error, password callback should handle it
}
setError(errorMessage);
setCurrentStep('error');
dispatch(setAuthError(errorMessage));
@@ -207,7 +207,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
const updateTimer = () => {
const remaining = Math.max(0, Math.floor((qrCodeExpires * 1000 - Date.now()) / 1000));
setTimeRemaining(remaining);
// If timer reaches 0 and we're on QR step, reload the QR code
if (remaining === 0 && currentStep === 'qr' && !isAuthenticating) {
startQrCodeFlow();
@@ -235,7 +235,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
if (!client) return;
const isAuthorized = await client.checkAuthorization();
if (isAuthorized) {
// User is authenticated - check if we need password
try {
@@ -277,12 +277,12 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
const handle2FASubmit = async () => {
const passwordValue = password.trim();
if (!passwordValue) {
setError('Please enter your password');
return;
}
if (!passwordResolverRef.current) {
console.error('Password resolver is null - password callback may not be active');
setError('Authentication session expired. Please try again.');
@@ -298,10 +298,10 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
// This will pass the password to the Telegram library
const resolver = passwordResolverRef.current;
passwordResolverRef.current = null; // Clear before resolving to prevent double calls
console.log('Resolving password promise - sending password to Telegram');
resolver(passwordValue);
// The authentication will continue in the background via signInWithQrCode
// The success/error will be handled by the signInWithQrCode promise
// We keep isAuthenticating true until the promise resolves or rejects
@@ -340,9 +340,9 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
if (!isOpen) return null;
const modalContent = (
<div
<div
className="fixed inset-0 z-[9999] bg-black/80 backdrop-blur-sm flex items-center justify-center"
style={{
style={{
position: 'fixed',
top: 0,
left: 0,
@@ -353,7 +353,7 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
zIndex: 9999
}}
>
<div
<div
className="bg-black/90 shadow-large animate-fade-up max-w-4xl max-h-[90vh] overflow-y-auto flex flex-col items-center justify-center rounded-3xl"
style={{
maxWidth: '56rem',
@@ -361,183 +361,183 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
padding: 0
}}
>
{/* Close button */}
<button
onClick={onClose}
className="absolute top-6 right-6 z-10 w-8 h-8 flex items-center justify-center rounded-full hover:bg-stone-800/50 transition-colors"
>
<svg className="w-5 h-5 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Close button */}
<button
onClick={onClose}
className="absolute top-6 right-6 z-10 w-8 h-8 flex items-center justify-center rounded-full hover:bg-stone-800/50 transition-colors"
>
<svg className="w-5 h-5 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{currentStep === 'loading' ? (
<div className="text-center py-8 flex flex-col items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500 mb-4"></div>
<p className="opacity-70">Initializing Telegram connection...</p>
{currentStep === 'loading' ? (
<div className="text-center py-8 flex flex-col items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500 mb-4"></div>
<p className="opacity-70">Initializing Telegram connection...</p>
</div>
) : currentStep === 'error' ? (
<>
{/* Error Screen */}
<div className="text-center flex flex-col items-center justify-center">
<div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 className="text-2xl font-bold mb-2">Connection Error</h2>
<p className="opacity-70 text-sm mb-6">{error || 'An error occurred'}</p>
<div className="flex space-x-3">
<button
onClick={handleBack}
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200"
>
Cancel
</button>
<button
onClick={handleRetry}
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white rounded-xl text-sm font-medium transition-all duration-200"
>
Retry
</button>
</div>
</div>
) : currentStep === 'error' ? (
<>
{/* Error Screen */}
<div className="text-center flex flex-col items-center justify-center">
<div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 className="text-2xl font-bold mb-2">Connection Error</h2>
<p className="opacity-70 text-sm mb-6">{error || 'An error occurred'}</p>
<div className="flex space-x-3">
<button
onClick={handleBack}
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200"
>
Cancel
</button>
<button
onClick={handleRetry}
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white rounded-xl text-sm font-medium transition-all duration-200"
>
Retry
</button>
</>
) : currentStep === 'qr' ? (
<>
{/* QR Code Screen */}
<div className="text-center flex flex-col items-center justify-center w-full">
{/* QR Code Container */}
<div className="flex justify-center mb-8">
<div className="bg-white p-4 rounded-2xl shadow-large">
{qrCodeUrl ? (
<div className="relative w-64 h-64 flex items-center justify-center">
<QRCodeSVG
value={qrCodeUrl}
size={256}
level="H"
includeMargin={true}
marginSize={1}
bgColor="#FFFFFF"
fgColor="#000000"
className="w-full h-full"
/>
</div>
) : (
<div className="w-64 h-64 bg-gray-100 rounded-xl flex items-center justify-center">
{isAuthenticating ? (
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600 text-sm">Generating QR code...</p>
</div>
) : (
<p className="text-gray-600 text-sm">Loading QR code...</p>
)}
</div>
)}
</div>
</div>
</>
) : currentStep === 'qr' ? (
<>
{/* QR Code Screen */}
<div className="text-center flex flex-col items-center justify-center w-full">
{/* QR Code Container */}
<div className="flex justify-center mb-8">
<div className="bg-white p-4 rounded-2xl shadow-large">
{qrCodeUrl ? (
<div className="relative w-64 h-64 flex items-center justify-center">
<QRCodeSVG
value={qrCodeUrl}
size={256}
level="H"
includeMargin={true}
marginSize={1}
bgColor="#FFFFFF"
fgColor="#000000"
className="w-full h-full"
/>
</div>
) : (
<div className="w-64 h-64 bg-gray-100 rounded-xl flex items-center justify-center">
{isAuthenticating ? (
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600 text-sm">Generating QR code...</p>
</div>
) : (
<p className="text-gray-600 text-sm">Loading QR code...</p>
)}
</div>
)}
</div>
</div>
{qrCodeExpires && timeRemaining > 0 && (
<p className="text-xs opacity-70 mb-4">
This code expires in {timeRemaining} seconds
</p>
)}
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
{/* Instructions */}
<div className="space-y-4 mb-6">
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">1</span>
</div>
<p className="opacity-90 text-sm">Open Telegram on your phone</p>
</div>
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">2</span>
</div>
<p className="opacity-90 text-sm">Go to Settings &gt; Devices &gt; Link Desktop Device</p>
</div>
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">3</span>
</div>
<p className="opacity-90 text-sm">Point your phone at this screen to confirm login</p>
</div>
</div>
</div>
</>
) : (
<>
{/* 2FA Screen */}
<div className="text-center flex flex-col items-center justify-center w-full">
<h2 className="text-2xl font-bold mb-2">Enter Your Password</h2>
<p className="opacity-70 text-sm mb-6">
{passwordHint
? `Your account is protected with two-step verification. Hint: ${passwordHint}`
: 'Your account is protected with two-step verification. Please enter your password to continue.'}
{qrCodeExpires && timeRemaining > 0 && (
<p className="text-xs opacity-70 mb-4">
This code expires in {timeRemaining} seconds
</p>
)}
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400 text-sm">{error}</p>
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
{/* Instructions */}
<div className="space-y-4 mb-6">
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">1</span>
</div>
)}
{/* Password input */}
<div className="mb-6">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && password.trim() && !isAuthenticating) {
handle2FASubmit();
}
}}
placeholder="Enter your password"
className="w-full px-4 py-3 bg-black/50 border border-stone-700 rounded-xl text-white placeholder-opacity-50 focus:outline-none focus:border-primary-500 transition-colors"
autoComplete="off"
data-form-type="other"
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
data-dashlane-ignore="true"
data-bitwarden-watching="false"
autoFocus
disabled={isAuthenticating}
/>
<p className="opacity-90 text-sm">Open Telegram on your phone</p>
</div>
{/* Action buttons */}
<div className="flex space-x-3">
<button
onClick={handleBack}
disabled={isAuthenticating}
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200 disabled:opacity-50"
>
Back
</button>
<button
onClick={handle2FASubmit}
disabled={!password.trim() || isAuthenticating}
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl text-sm font-medium transition-all duration-200"
>
{isAuthenticating ? 'Verifying...' : 'Continue'}
</button>
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">2</span>
</div>
<p className="opacity-90 text-sm">Go to Settings &gt; Devices &gt; Link Desktop Device</p>
</div>
<div className="flex items-start space-x-3 text-left">
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-white font-bold text-xs">3</span>
</div>
<p className="opacity-90 text-sm">Point your phone at this screen to confirm login</p>
</div>
</div>
</>
)}
</div>
</div>
</>
) : (
<>
{/* 2FA Screen */}
<div className="text-center flex flex-col items-center justify-center w-full">
<h2 className="text-2xl font-bold mb-2">Enter Your Password</h2>
<p className="opacity-70 text-sm mb-6">
{passwordHint
? `Your account is protected with two-step verification. Hint: ${passwordHint}`
: 'Your account is protected with two-step verification. Please enter your password to continue.'}
</p>
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
{/* Password input */}
<div className="mb-6">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && password.trim() && !isAuthenticating) {
handle2FASubmit();
}
}}
placeholder="Enter your password"
className="w-full px-4 py-3 bg-black/50 border border-stone-700 rounded-xl text-white placeholder-opacity-50 focus:outline-none focus:border-primary-500 transition-colors"
autoComplete="off"
data-form-type="other"
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
data-dashlane-ignore="true"
data-bitwarden-watching="false"
autoFocus
disabled={isAuthenticating}
/>
</div>
{/* Action buttons */}
<div className="flex space-x-3">
<button
onClick={handleBack}
disabled={isAuthenticating}
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200 disabled:opacity-50"
>
Back
</button>
<button
onClick={handle2FASubmit}
disabled={!password.trim() || isAuthenticating}
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl text-sm font-medium transition-all duration-200"
>
{isAuthenticating ? 'Verifying...' : 'Continue'}
</button>
</div>
</div>
</>
)}
</div>
</div>
);
+4 -4
View File
@@ -2,10 +2,10 @@ import { useState } from 'react';
import GoogleIcon from '../../../assets/icons/GoogleIcon';
import TelegramConnectionModal from '../../../components/TelegramConnectionModal';
import BinanceIcon from './icons/binance.svg';
import NotionIcon from './icons/notion.svg';
import TelegramIcon from './icons/telegram.svg';
import MetamaskIcon from './icons/metamask.svg';
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';
interface ConnectStepProps {
+81 -25
View File
@@ -1,4 +1,4 @@
import { PayloadAction } from '@reduxjs/toolkit';
import { PayloadAction } from "@reduxjs/toolkit";
import type {
TelegramState,
TelegramConnectionStatus,
@@ -7,50 +7,74 @@ import type {
TelegramChat,
TelegramMessage,
TelegramThread,
} from './types';
} from "./types";
export const reducers = {
// Connection actions
setConnectionStatus: (state: TelegramState, action: PayloadAction<TelegramConnectionStatus>) => {
setConnectionStatus: (
state: TelegramState,
action: PayloadAction<TelegramConnectionStatus>,
) => {
state.connectionStatus = action.payload;
if (action.payload !== 'error') {
if (action.payload !== "error") {
state.connectionError = null;
}
},
setConnectionError: (state: TelegramState, action: PayloadAction<string | null>) => {
setConnectionError: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.connectionError = action.payload;
if (action.payload) {
state.connectionStatus = 'error';
state.connectionStatus = "error";
}
},
// Authentication actions
setAuthStatus: (state: TelegramState, action: PayloadAction<TelegramAuthStatus>) => {
setAuthStatus: (
state: TelegramState,
action: PayloadAction<TelegramAuthStatus>,
) => {
state.authStatus = action.payload;
if (action.payload !== 'error') {
if (action.payload !== "error") {
state.authError = null;
}
},
setAuthError: (state: TelegramState, action: PayloadAction<string | null>) => {
setAuthError: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.authError = action.payload;
if (action.payload) {
state.authStatus = 'error';
state.authStatus = "error";
}
},
setPhoneNumber: (state: TelegramState, action: PayloadAction<string | null>) => {
setPhoneNumber: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.phoneNumber = action.payload;
},
setSessionString: (state: TelegramState, action: PayloadAction<string | null>) => {
setSessionString: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.sessionString = action.payload;
},
// User actions
setCurrentUser: (state: TelegramState, action: PayloadAction<TelegramUser | null>) => {
setCurrentUser: (
state: TelegramState,
action: PayloadAction<TelegramUser | null>,
) => {
state.currentUser = action.payload;
},
// Chat actions
setChats: (state: TelegramState, action: PayloadAction<Record<string, TelegramChat>>) => {
setChats: (
state: TelegramState,
action: PayloadAction<Record<string, TelegramChat>>,
) => {
state.chats = action.payload;
},
addChat: (state: TelegramState, action: PayloadAction<TelegramChat>) => {
@@ -60,7 +84,10 @@ export const reducers = {
state.chatsOrder.unshift(chat.id);
}
},
updateChat: (state: TelegramState, action: PayloadAction<Partial<TelegramChat> & { id: string }>) => {
updateChat: (
state: TelegramState,
action: PayloadAction<Partial<TelegramChat> & { id: string }>,
) => {
const { id, ...updates } = action.payload;
if (state.chats[id]) {
state.chats[id] = { ...state.chats[id], ...updates };
@@ -74,7 +101,10 @@ export const reducers = {
state.selectedChatId = null;
}
},
setSelectedChat: (state: TelegramState, action: PayloadAction<string | null>) => {
setSelectedChat: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.selectedChatId = action.payload;
// Clear selected thread when changing chat
if (action.payload !== state.selectedChatId) {
@@ -86,7 +116,10 @@ export const reducers = {
},
// Message actions
addMessage: (state: TelegramState, action: PayloadAction<TelegramMessage>) => {
addMessage: (
state: TelegramState,
action: PayloadAction<TelegramMessage>,
) => {
const message = action.payload;
const { chatId, id } = message;
@@ -100,7 +133,10 @@ export const reducers = {
state.messagesOrder[chatId].push(id);
}
},
addMessages: (state: TelegramState, action: PayloadAction<{ chatId: string; messages: TelegramMessage[] }>) => {
addMessages: (
state: TelegramState,
action: PayloadAction<{ chatId: string; messages: TelegramMessage[] }>,
) => {
const { chatId, messages } = action.payload;
if (!state.messages[chatId]) {
@@ -117,7 +153,11 @@ export const reducers = {
},
updateMessage: (
state: TelegramState,
action: PayloadAction<{ chatId: string; messageId: string; updates: Partial<TelegramMessage> }>
action: PayloadAction<{
chatId: string;
messageId: string;
updates: Partial<TelegramMessage>;
}>,
) => {
const { chatId, messageId, updates } = action.payload;
if (state.messages[chatId]?.[messageId]) {
@@ -127,12 +167,15 @@ export const reducers = {
};
}
},
removeMessage: (state: TelegramState, action: PayloadAction<{ chatId: string; messageId: string }>) => {
removeMessage: (
state: TelegramState,
action: PayloadAction<{ chatId: string; messageId: string }>,
) => {
const { chatId, messageId } = action.payload;
if (state.messages[chatId]?.[messageId]) {
delete state.messages[chatId][messageId];
state.messagesOrder[chatId] = state.messagesOrder[chatId].filter(
(id) => id !== messageId
(id) => id !== messageId,
);
}
},
@@ -159,7 +202,11 @@ export const reducers = {
},
updateThread: (
state: TelegramState,
action: PayloadAction<{ chatId: string; threadId: string; updates: Partial<TelegramThread> }>
action: PayloadAction<{
chatId: string;
threadId: string;
updates: Partial<TelegramThread>;
}>,
) => {
const { chatId, threadId, updates } = action.payload;
if (state.threads[chatId]?.[threadId]) {
@@ -169,15 +216,24 @@ export const reducers = {
};
}
},
setSelectedThread: (state: TelegramState, action: PayloadAction<string | null>) => {
setSelectedThread: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.selectedThreadId = action.payload;
},
// Search actions
setSearchQuery: (state: TelegramState, action: PayloadAction<string | null>) => {
setSearchQuery: (
state: TelegramState,
action: PayloadAction<string | null>,
) => {
state.searchQuery = action.payload;
},
setFilteredChatIds: (state: TelegramState, action: PayloadAction<string[] | null>) => {
setFilteredChatIds: (
state: TelegramState,
action: PayloadAction<string[] | null>,
) => {
state.filteredChatIds = action.payload;
},