From bbd2665f808f84c9b9cf4cb5eac59d0bcc1320ce Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 15:36:51 +0400 Subject: [PATCH 01/11] fix: re-authorization issue --- src/hooks/useDerivWebSocket.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/hooks/useDerivWebSocket.js b/src/hooks/useDerivWebSocket.js index 921c189..35dd960 100644 --- a/src/hooks/useDerivWebSocket.js +++ b/src/hooks/useDerivWebSocket.js @@ -10,9 +10,10 @@ console.log('WebSocket Configuration:', { FINAL_URL: WEBSOCKET_URL }) -// Singleton WebSocket instance +// Singleton WebSocket instance and state let globalWs = null let responseHandlers = new Set() +let isAuthorizedGlobal = false const useDerivWebSocket = () => { const [socket, setSocket] = useState(null) @@ -56,6 +57,7 @@ const useDerivWebSocket = () => { } else { console.log('Authorization successful') authRetryCountRef.current = 0 // Reset retry counter on success + isAuthorizedGlobal = true setIsConnected(true) globalWs.send(JSON.stringify({ get_settings: 1 @@ -91,10 +93,12 @@ const useDerivWebSocket = () => { globalWs.onopen = () => { console.log('WebSocket connected, readyState:', globalWs.readyState) setIsConnected(true) - console.log('Sending authorize request with token:', defaultAccount.token) - globalWs.send(JSON.stringify({ - authorize: defaultAccount.token - })) + if (!isAuthorizedGlobal) { + console.log('Sending authorize request with token:', defaultAccount.token) + globalWs.send(JSON.stringify({ + authorize: defaultAccount.token + })) + } } globalWs.onerror = (error) => { @@ -104,6 +108,7 @@ const useDerivWebSocket = () => { globalWs.onclose = () => { console.log('WebSocket connection closed') globalWs = null + isAuthorizedGlobal = false responseHandlers.forEach(handler => handler({ type: 'connection', status: 'disconnected' })) setIsConnected(false) } @@ -118,7 +123,7 @@ const useDerivWebSocket = () => { responseHandlers.add(handleResponseRef.current) setSocket(globalWs) - if (globalWs.readyState === WebSocket.OPEN) { + if (globalWs.readyState === WebSocket.OPEN && !isAuthorizedGlobal) { setIsConnected(true) console.log('WebSocket already open, sending authorize request') globalWs.send(JSON.stringify({ @@ -159,4 +164,4 @@ const useDerivWebSocket = () => { } } -export default useDerivWebSocket +export default useDerivWebSocket From fa57c9f32137a6120cccd00cff99b4ed138b8c74 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 16:23:26 +0400 Subject: [PATCH 02/11] feat: added pure ws logic in useWebSocket hook --- src/hooks/useWebSocket.js | 152 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/hooks/useWebSocket.js diff --git a/src/hooks/useWebSocket.js b/src/hooks/useWebSocket.js new file mode 100644 index 0000000..5a43f11 --- /dev/null +++ b/src/hooks/useWebSocket.js @@ -0,0 +1,152 @@ +import { useState, useEffect, useCallback } from 'react'; + +// Singleton WebSocket instance +let wsInstance = null; +let wsUrl = null; +let subscribers = new Set(); +let messageHandlers = new Map(); +let currentReqId = 1; +let pingInterval = null; + +// Ping interval in milliseconds (30 seconds) +const PING_INTERVAL = 30000; + +const sendPing = () => { + if (wsInstance?.readyState === WebSocket.OPEN) { + const reqId = generateReqId(); + wsInstance.send(JSON.stringify({ ping: 1, req_id: reqId })); + } +}; + +const generateReqId = () => { + return currentReqId++; +}; + +const createWebSocket = (url) => { + if (wsInstance && wsUrl === url) { + return wsInstance; + } + + wsUrl = url; + wsInstance = new WebSocket(url); + + wsInstance.onopen = () => { + subscribers.forEach(subscriber => subscriber.onOpen?.()); + // Start ping interval + if (pingInterval) { + clearInterval(pingInterval); + } + pingInterval = setInterval(sendPing, PING_INTERVAL); + }; + + wsInstance.onclose = () => { + subscribers.forEach(subscriber => subscriber.onClose?.()); + wsInstance = null; + messageHandlers.clear(); + // Clear ping interval + if (pingInterval) { + clearInterval(pingInterval); + pingInterval = null; + } + }; + + wsInstance.onerror = (error) => { + subscribers.forEach(subscriber => subscriber.onError?.(error)); + }; + + wsInstance.onmessage = (event) => { + try { + const response = JSON.parse(event.data); + const reqId = response.req_id; + + // Handle specific message handler if exists + if (reqId && messageHandlers.has(reqId)) { + messageHandlers.get(reqId)(response); + messageHandlers.delete(reqId); + } + + // Broadcast to all subscribers + subscribers.forEach(subscriber => subscriber.onMessage?.(response)); + } catch (error) { + console.error('Failed to parse WebSocket message:', error); + } + }; + + return wsInstance; +}; + +const useWebSocket = (url) => { + const [isConnected, setIsConnected] = useState(false); + const [error, setError] = useState(null); + const [lastMessage, setLastMessage] = useState(null); + + useEffect(() => { + const subscriber = { + onOpen: () => { + setIsConnected(true); + setError(null); + }, + onClose: () => { + setIsConnected(false); + }, + onError: (error) => { + setError(error); + setIsConnected(false); + }, + onMessage: (message) => { + setLastMessage(message); + } + }; + + subscribers.add(subscriber); + const ws = createWebSocket(url); + + // If WebSocket is already open when hook is initialized + if (ws.readyState === WebSocket.OPEN) { + setIsConnected(true); + } + + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0 && wsInstance) { + wsInstance.close(); + wsInstance = null; + } + }; + }, [url]); + + const sendMessage = useCallback((message, callback) => { + if (!wsInstance || wsInstance.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not connected'); + } + + const reqId = generateReqId(); + const messageWithId = { + ...message, + req_id: reqId + }; + + if (callback) { + messageHandlers.set(reqId, callback); + } + + wsInstance.send(JSON.stringify(messageWithId)); + return reqId; + }, []); + + const close = useCallback(() => { + if (wsInstance) { + wsInstance.close(); + } + }, []); + + return { + isConnected, + error, + lastMessage, + sendMessage, + close + }; +}; + +export default useWebSocket; From edd54693732a6a1aab75b67de965188de8003d0a Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 16:23:44 +0400 Subject: [PATCH 03/11] feat: added useAuthorize hook --- src/hooks/useAuthorize.js | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/hooks/useAuthorize.js diff --git a/src/hooks/useAuthorize.js b/src/hooks/useAuthorize.js new file mode 100644 index 0000000..b111cdc --- /dev/null +++ b/src/hooks/useAuthorize.js @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import useWebSocket from './useWebSocket'; +import useDerivAccounts from './useDerivAccounts'; +import { getConfig } from '../config'; + +const config = getConfig(); +const WEBSOCKET_URL = `${config.WS_URL}?app_id=${config.APP_ID}`; + +// Singleton state +let isAuthorizedGlobal = false; +let authErrorGlobal = null; + +const useAuthorize = () => { + const { defaultAccount, clearAccounts } = useDerivAccounts(); + const { isConnected, sendMessage, close } = useWebSocket(WEBSOCKET_URL); + + // Handle authorization + useEffect(() => { + if (isConnected && defaultAccount?.token && !isAuthorizedGlobal) { + console.log('Sending authorize request'); + sendMessage( + { authorize: defaultAccount.token }, + (response) => { + if (response.error) { + console.error('Authorization failed:', response.error); + authErrorGlobal = response.error; + isAuthorizedGlobal = false; + clearAccounts(); + close(); + } else { + console.log('Authorization successful'); + authErrorGlobal = null; + isAuthorizedGlobal = true; + } + } + ); + } + }, [isConnected, defaultAccount, sendMessage, clearAccounts, close]); + + // Reset auth state when connection is lost + useEffect(() => { + if (!isConnected) { + isAuthorizedGlobal = false; + } + }, [isConnected]); + + return { + isAuthorized: isAuthorizedGlobal, + authError: authErrorGlobal, + isConnected, + }; +}; + +export default useAuthorize; From 7fefacf422d0e11e706e6c7614055eca7cdc0a0a Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 16:24:02 +0400 Subject: [PATCH 04/11] feat: added useSettings hook --- src/hooks/useSettings.js | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/hooks/useSettings.js diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js new file mode 100644 index 0000000..afb3e3b --- /dev/null +++ b/src/hooks/useSettings.js @@ -0,0 +1,79 @@ +import { useState, useEffect, useCallback } from 'react'; +import useWebSocket from './useWebSocket'; +import useAuthorize from './useAuthorize'; +import { getConfig } from '../config'; + +const config = getConfig(); +const WEBSOCKET_URL = `${config.WS_URL}?app_id=${config.APP_ID}`; + +const useSettings = () => { + const [settings, setSettings] = useState(null); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const { isAuthorized } = useAuthorize(); + const { isConnected, sendMessage } = useWebSocket(WEBSOCKET_URL); + + // Fetch settings when authorized + useEffect(() => { + if (isConnected && isAuthorized && !settings) { + console.log('Fetching user settings'); + sendMessage( + { get_settings: 1 }, + (response) => { + if (response.error) { + console.error('Failed to fetch settings:', response.error); + setError(response.error); + } else { + console.log('Settings received:', response.get_settings); + setSettings(response.get_settings); + setError(null); + } + setIsLoading(false); + } + ); + } + }, [isConnected, isAuthorized, settings, sendMessage]); + + // Reset settings when connection is lost + useEffect(() => { + if (!isConnected) { + setSettings(null); + setIsLoading(true); + } + }, [isConnected]); + + const updateSettings = useCallback(async (newSettings) => { + if (!isConnected || !isAuthorized) { + throw new Error('Not connected or authorized'); + } + + return new Promise((resolve, reject) => { + sendMessage( + { set_settings: 1, ...newSettings }, + (response) => { + if (response.error) { + console.error('Failed to update settings:', response.error); + setError(response.error); + reject(response.error); + } else { + console.log('Settings updated:', response.set_settings); + setSettings(response.set_settings); + setError(null); + resolve(response.set_settings); + } + } + ); + }); + }, [isConnected, isAuthorized, sendMessage]); + + return { + settings, + error, + isLoading, + updateSettings, + isConnected, + isAuthorized + }; +}; + +export default useSettings; From 4fd8be6b194050d19451a8505be30a1dd2536f4e Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 17:22:21 +0400 Subject: [PATCH 05/11] fix: useWebSocket to use config for connecting to ws --- src/hooks/useAuthorize.js | 6 +----- src/hooks/useSettings.js | 10 ++-------- src/hooks/useWebSocket.js | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/hooks/useAuthorize.js b/src/hooks/useAuthorize.js index b111cdc..aa760e0 100644 --- a/src/hooks/useAuthorize.js +++ b/src/hooks/useAuthorize.js @@ -1,10 +1,6 @@ import { useEffect } from 'react'; import useWebSocket from './useWebSocket'; import useDerivAccounts from './useDerivAccounts'; -import { getConfig } from '../config'; - -const config = getConfig(); -const WEBSOCKET_URL = `${config.WS_URL}?app_id=${config.APP_ID}`; // Singleton state let isAuthorizedGlobal = false; @@ -12,7 +8,7 @@ let authErrorGlobal = null; const useAuthorize = () => { const { defaultAccount, clearAccounts } = useDerivAccounts(); - const { isConnected, sendMessage, close } = useWebSocket(WEBSOCKET_URL); + const { isConnected, sendMessage, close } = useWebSocket(); // Handle authorization useEffect(() => { diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js index afb3e3b..79a6e93 100644 --- a/src/hooks/useSettings.js +++ b/src/hooks/useSettings.js @@ -1,17 +1,13 @@ import { useState, useEffect, useCallback } from 'react'; import useWebSocket from './useWebSocket'; import useAuthorize from './useAuthorize'; -import { getConfig } from '../config'; - -const config = getConfig(); -const WEBSOCKET_URL = `${config.WS_URL}?app_id=${config.APP_ID}`; const useSettings = () => { const [settings, setSettings] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); const { isAuthorized } = useAuthorize(); - const { isConnected, sendMessage } = useWebSocket(WEBSOCKET_URL); + const { isConnected, sendMessage } = useWebSocket(); // Fetch settings when authorized useEffect(() => { @@ -70,9 +66,7 @@ const useSettings = () => { settings, error, isLoading, - updateSettings, - isConnected, - isAuthorized + updateSettings }; }; diff --git a/src/hooks/useWebSocket.js b/src/hooks/useWebSocket.js index 5a43f11..23ee47e 100644 --- a/src/hooks/useWebSocket.js +++ b/src/hooks/useWebSocket.js @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback } from 'react'; +import { getConfig } from '../config'; // Singleton WebSocket instance let wsInstance = null; -let wsUrl = null; let subscribers = new Set(); let messageHandlers = new Map(); let currentReqId = 1; @@ -22,13 +22,14 @@ const generateReqId = () => { return currentReqId++; }; -const createWebSocket = (url) => { - if (wsInstance && wsUrl === url) { +const createWebSocket = () => { + const config = getConfig(); + const wsUrl = config.WS_URL; + + if (wsInstance) { return wsInstance; } - - wsUrl = url; - wsInstance = new WebSocket(url); + wsInstance = new WebSocket(wsUrl); wsInstance.onopen = () => { subscribers.forEach(subscriber => subscriber.onOpen?.()); @@ -75,7 +76,7 @@ const createWebSocket = (url) => { return wsInstance; }; -const useWebSocket = (url) => { +const useWebSocket = () => { const [isConnected, setIsConnected] = useState(false); const [error, setError] = useState(null); const [lastMessage, setLastMessage] = useState(null); @@ -99,7 +100,7 @@ const useWebSocket = (url) => { }; subscribers.add(subscriber); - const ws = createWebSocket(url); + const ws = createWebSocket(); // If WebSocket is already open when hook is initialized if (ws.readyState === WebSocket.OPEN) { @@ -113,7 +114,7 @@ const useWebSocket = (url) => { wsInstance = null; } }; - }, [url]); + }, []); const sendMessage = useCallback((message, callback) => { if (!wsInstance || wsInstance.readyState !== WebSocket.OPEN) { From 01ce0166920b0d544661225e7682ed909969b324 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 17:57:39 +0400 Subject: [PATCH 06/11] fix: url formation and close on unmount issue in useWebSocket --- src/hooks/useWebSocket.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/hooks/useWebSocket.js b/src/hooks/useWebSocket.js index 23ee47e..f4f3dcb 100644 --- a/src/hooks/useWebSocket.js +++ b/src/hooks/useWebSocket.js @@ -24,7 +24,8 @@ const generateReqId = () => { const createWebSocket = () => { const config = getConfig(); - const wsUrl = config.WS_URL; + const { WS_URL, APP_ID } = config; + const wsUrl = `${WS_URL}?app_id=${APP_ID}`; if (wsInstance) { return wsInstance; @@ -109,10 +110,6 @@ const useWebSocket = () => { return () => { subscribers.delete(subscriber); - if (subscribers.size === 0 && wsInstance) { - wsInstance.close(); - wsInstance = null; - } }; }, []); From c2c846e0c610217d40e524b32e71a6440486719e Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 18:03:24 +0400 Subject: [PATCH 07/11] refactor: Dashboard to use useWebSocket instead of useDerivWebSocket --- src/components/Dashboard.jsx | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 6bd101b..c64b4af 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -1,40 +1,23 @@ import { useState } from "react"; -import { - Button, - Text, - SegmentedControlSingleChoice, -} from "@deriv-com/quill-ui"; -import useDerivWebSocket from "../hooks/useDerivWebSocket"; +import { Button } from "@deriv-com/quill-ui"; +import useWebSocket from "../hooks/useWebSocket"; import Header from "./Header"; import TraderDashboard from "./TraderDashboard"; import CopierDashboard from "./CopierDashboard"; const Dashboard = () => { - const { settings, isLoading, sendRequest } = useDerivWebSocket(); + const { isConnected } = useWebSocket(); const [userType, setUserType] = useState("trader"); - const items = [ - { label: "Trader", value: "trader" }, - { label: "Copier", value: "copier" }, - ]; - const handleBecomeTrader = () => { - sendRequest({ - set_settings: 1, - allow_copiers: 1, - }); setUserType("trader"); }; const handleBecomeCopier = () => { - sendRequest({ - set_settings: 1, - allow_copiers: 0, - }); setUserType("copier"); }; - if (isLoading) { + if (!isConnected) { return
Loading...
; } From 0ad280211006da6ac452dc94f261631c741342a5 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 18:20:12 +0400 Subject: [PATCH 08/11] refactor: useCopyTradingList and useCopyTradingStats with useWebSocket --- src/hooks/useCopyTradersList.js | 71 ++++++++++++++++++++++---------- src/hooks/useCopyTradingStats.js | 33 +++++++++------ 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/hooks/useCopyTradersList.js b/src/hooks/useCopyTradersList.js index 7a3d1c5..22457c5 100644 --- a/src/hooks/useCopyTradersList.js +++ b/src/hooks/useCopyTradersList.js @@ -1,50 +1,77 @@ -import { useEffect, useState } from 'react' -import useDerivWebSocket from './useDerivWebSocket' +import { useEffect, useState, useCallback } from 'react' +import useWebSocket from './useWebSocket' +import useAuthorize from './useAuthorize' const useCopyTradersList = () => { - const { sendRequest, wsResponse } = useDerivWebSocket() + const { sendMessage, lastMessage } = useWebSocket() + const { isAuthorized, isConnected } = useAuthorize() const [traders, setTraders] = useState([]) const [copiers, setCopiers] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) - useEffect(() => { - // Make the initial request - sendRequest({ - copytrading_list: 1, - // Optional parameters can be added here: - // sort_fields: ["performance", "monthly_profitable_trades"], - // sort_order: ["DESC", "DESC"] - }) - }, [sendRequest]) + const fetchList = useCallback(() => { + if (!isConnected || !isAuthorized) return + + sendMessage( + { + copytrading_list: 1, + // Optional parameters can be added here: + // sort_fields: ["performance", "monthly_profitable_trades"], + // sort_order: ["DESC", "DESC"] + }, + (response) => { + console.log('Copy Trading List Response:', response) + if (response.error) { + setError(response.error.message) + setIsLoading(false) + return + } + + if (response.copytrading_list) { + setTraders(response.copytrading_list.traders) + setCopiers(response.copytrading_list.copiers) + setIsLoading(false) + setError(null) + } + } + ) + }, [sendMessage, isConnected, isAuthorized]) + + // Initial fetch when authorized and connected useEffect(() => { - if (!wsResponse) return + if (isAuthorized && isConnected) { + fetchList() + } + }, [isAuthorized, isConnected, fetchList]) - if (wsResponse.msg_type === 'copytrading_list') { - console.log('Copy Trading List Response:', wsResponse) + // Handle broadcast messages + useEffect(() => { + if (!lastMessage) return - if (wsResponse.error) { - setError(wsResponse.error.message) + if (lastMessage.msg_type === 'copytrading_list') { + if (lastMessage.error) { + setError(lastMessage.error.message) setIsLoading(false) return } - if (wsResponse.copytrading_list) { - setTraders(wsResponse.copytrading_list.traders) - setCopiers(wsResponse.copytrading_list.copiers) + if (lastMessage.copytrading_list) { + setTraders(lastMessage.copytrading_list.traders) + setCopiers(lastMessage.copytrading_list.copiers) setIsLoading(false) setError(null) } } - }, [wsResponse]) + }, [lastMessage]) return { traders, copiers, isLoading, error, - refreshList: () => sendRequest({ copytrading_list: 1 }) + refreshList: fetchList } } diff --git a/src/hooks/useCopyTradingStats.js b/src/hooks/useCopyTradingStats.js index e4fddfa..480ee3e 100644 --- a/src/hooks/useCopyTradingStats.js +++ b/src/hooks/useCopyTradingStats.js @@ -1,42 +1,46 @@ import { useState, useEffect } from 'react'; -import useDerivWebSocket from './useDerivWebSocket'; +import useWebSocket from './useWebSocket'; const useCopyTradingStats = (traderId) => { const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const { sendRequest, wsResponse } = useDerivWebSocket(); + const { sendMessage, lastMessage } = useWebSocket(); useEffect(() => { if (!traderId) return; const fetchStats = () => { setIsLoading(true); - sendRequest({ + sendMessage({ copytrading_statistics: 1, trader_id: traderId, + passthrough: { + trader_id: traderId + } }); }; fetchStats(); - }, [traderId, sendRequest]); + }, [traderId, sendMessage]); useEffect(() => { - if (!wsResponse || !traderId) return; + if (!lastMessage || !traderId) return; - if (wsResponse.echo_req?.trader_id === traderId) { - if (wsResponse.error) { - setError(wsResponse.error.message); + // Check if this response is for the current trader + if (lastMessage.passthrough?.trader_id === traderId) { + if (lastMessage.error) { + setError(lastMessage.error.message); setIsLoading(false); return; } - if (wsResponse.msg_type === 'copytrading_statistics') { - setStats(wsResponse.copytrading_statistics); + if (lastMessage.msg_type === 'copytrading_statistics') { + setStats(lastMessage.copytrading_statistics); setIsLoading(false); } } - }, [wsResponse, traderId]); + }, [lastMessage, traderId]); return { stats, @@ -44,12 +48,15 @@ const useCopyTradingStats = (traderId) => { error, refetch: () => { setError(null); - sendRequest({ + sendMessage({ copytrading_statistics: 1, trader_id: traderId, + passthrough: { + trader_id: traderId + } }); }, }; }; -export default useCopyTradingStats; \ No newline at end of file +export default useCopyTradingStats; From a80051294464eb6c574de6bf799a905bd9c2b4a0 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Mon, 23 Dec 2024 18:29:58 +0400 Subject: [PATCH 09/11] refactor: CopierDashboard and AddTraderForm to use the new websocket implementation --- src/components/AddTraderForm.jsx | 26 +++++++++++++++------- src/components/CopierDashboard.jsx | 35 +++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/components/AddTraderForm.jsx b/src/components/AddTraderForm.jsx index cf823c4..53bd609 100644 --- a/src/components/AddTraderForm.jsx +++ b/src/components/AddTraderForm.jsx @@ -1,14 +1,16 @@ import { useState, useEffect } from "react"; import { Text, Button, TextField, Snackbar } from "@deriv-com/quill-ui"; import PropTypes from "prop-types"; -import useDerivWebSocket from "../hooks/useDerivWebSocket"; +import useWebSocket from "../hooks/useWebSocket"; +import useAuthorize from "../hooks/useAuthorize"; const AddTraderForm = ({ onAddTrader }) => { const [traderData, setTraderData] = useState({ token: "", }); - const { sendRequest, wsResponse } = useDerivWebSocket(); + const { sendMessage, lastMessage } = useWebSocket(); + const { isAuthorized, isConnected } = useAuthorize(); const [isProcessing, setIsProcessing] = useState(false); const [snackbar, setSnackbar] = useState({ isVisible: false, @@ -17,16 +19,16 @@ const AddTraderForm = ({ onAddTrader }) => { }); useEffect(() => { - if (!wsResponse || !isProcessing) return; + if (!lastMessage || !isProcessing) return; - if (wsResponse.msg_type === "copy_start") { + if (lastMessage.msg_type === "copy_start") { setIsProcessing(false); - if (wsResponse.error) { + if (lastMessage.error) { setSnackbar({ isVisible: true, message: - wsResponse.error.message || + lastMessage.error.message || "Failed to start copy trading", status: "fail", }); @@ -40,12 +42,20 @@ const AddTraderForm = ({ onAddTrader }) => { }); } } - }, [wsResponse, isProcessing, onAddTrader, traderData]); + }, [lastMessage, isProcessing, onAddTrader, traderData]); const handleSubmit = (e) => { e.preventDefault(); + if (!isConnected || !isAuthorized) { + setSnackbar({ + isVisible: true, + message: "Not connected to server", + status: "fail", + }); + return; + } setIsProcessing(true); - sendRequest({ + sendMessage({ copy_start: traderData.token, }); }; diff --git a/src/components/CopierDashboard.jsx b/src/components/CopierDashboard.jsx index b258d93..57d443e 100644 --- a/src/components/CopierDashboard.jsx +++ b/src/components/CopierDashboard.jsx @@ -1,12 +1,15 @@ import { useEffect, useState } from "react"; import { Text, Snackbar } from "@deriv-com/quill-ui"; -import useDerivWebSocket from "../hooks/useDerivWebSocket"; +import useWebSocket from "../hooks/useWebSocket"; +import useAuthorize from "../hooks/useAuthorize"; import useCopyTradersList from "../hooks/useCopyTradersList"; import AddTraderForm from "./AddTraderForm"; import TraderCard from "./TraderCard"; const CopierDashboard = () => { - const { sendRequest, wsResponse } = useDerivWebSocket(); + const { sendMessage, isConnected } = useWebSocket(); + const { isAuthorized } = useAuthorize(); + const [wsResponse, setWsResponse] = useState(null); const { traders: apiTraders, isLoading, @@ -100,17 +103,33 @@ const CopierDashboard = () => { const handleCopyClick = (trader) => { console.log("Copy clicked for trader:", trader); + if (!isConnected || !isAuthorized) { + setSnackbar({ + isVisible: true, + message: "Connection not ready. Please try again.", + status: "fail", + }); + return; + } setProcessingTrader(trader); - sendRequest({ - copy_start: trader.token, + sendMessage({ copy_start: trader.token }, (response) => { + setWsResponse(response); }); }; const handleStopCopy = (trader) => { console.log("Stop copy clicked for trader:", trader); + if (!isConnected || !isAuthorized) { + setSnackbar({ + isVisible: true, + message: "Connection not ready. Please try again.", + status: "fail", + }); + return; + } setProcessingTrader(trader); - sendRequest({ - copy_stop: trader.token, + sendMessage({ copy_stop: trader.token }, (response) => { + setWsResponse(response); }); }; @@ -162,7 +181,11 @@ const CopierDashboard = () => { name: trader.name || `Trader ${trader.loginid}`, token: trader.token, }} + onCopyClick={handleCopyClick} onStopCopy={handleStopCopy} + onRemoveTrader={handleRemoveTrader} + isCopied={copiedTrader?.id === trader.loginid} + hasFailed={failedCopyTrader?.id === trader.loginid} /> ))} From 5af49ae5291f9f49dd1938d1882d1eb5ea4b0cc0 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 24 Dec 2024 11:36:00 +0400 Subject: [PATCH 10/11] style: change position of copier and trader buttons in Dashboard --- src/components/Dashboard.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index c64b4af..111ac7a 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -7,7 +7,7 @@ import CopierDashboard from "./CopierDashboard"; const Dashboard = () => { const { isConnected } = useWebSocket(); - const [userType, setUserType] = useState("trader"); + const [userType, setUserType] = useState("copier"); const handleBecomeTrader = () => { setUserType("trader"); @@ -29,21 +29,21 @@ const Dashboard = () => {
@@ -53,7 +53,7 @@ const Dashboard = () => { ) : userType === "copier" ? ( ) : ( - + )} From ff49b3208850a2116dea3add9368030f95352999 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 24 Dec 2024 12:08:00 +0400 Subject: [PATCH 11/11] chore: cleanup CopierDashboard code --- src/components/CopierDashboard.jsx | 38 +----------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/components/CopierDashboard.jsx b/src/components/CopierDashboard.jsx index 57d443e..ee0cd94 100644 --- a/src/components/CopierDashboard.jsx +++ b/src/components/CopierDashboard.jsx @@ -25,8 +25,6 @@ const CopierDashboard = () => { }); }, [apiTraders, isLoading, error]); const [processingTrader, setProcessingTrader] = useState(null); - const [copiedTrader, setCopiedTrader] = useState(null); - const [failedCopyTrader, setFailedCopyTrader] = useState(null); const [snackbar, setSnackbar] = useState({ isVisible: false, message: "", @@ -53,7 +51,6 @@ const CopierDashboard = () => { "Error starting copy trade", status: "fail", }); - setFailedCopyTrader(processingTrader); setProcessingTrader(null); } else { const trader = processingTrader; @@ -61,11 +58,10 @@ const CopierDashboard = () => { "Showing success snackbar for trader:", trader.name ); - setCopiedTrader(trader); setProcessingTrader(null); setSnackbar({ isVisible: true, - message: `Successfully started copying ${trader.name}`, + message: `Successfully started copying ${trader.id}`, status: "neutral", }); } @@ -81,7 +77,6 @@ const CopierDashboard = () => { setProcessingTrader(null); } else { const trader = processingTrader; - setCopiedTrader(null); setProcessingTrader(null); setSnackbar({ isVisible: true, @@ -101,22 +96,6 @@ const CopierDashboard = () => { setSnackbar((prev) => ({ ...prev, isVisible: false })); }; - const handleCopyClick = (trader) => { - console.log("Copy clicked for trader:", trader); - if (!isConnected || !isAuthorized) { - setSnackbar({ - isVisible: true, - message: "Connection not ready. Please try again.", - status: "fail", - }); - return; - } - setProcessingTrader(trader); - sendMessage({ copy_start: trader.token }, (response) => { - setWsResponse(response); - }); - }; - const handleStopCopy = (trader) => { console.log("Stop copy clicked for trader:", trader); if (!isConnected || !isAuthorized) { @@ -139,17 +118,6 @@ const CopierDashboard = () => { refreshList(); }; - const handleRemoveTrader = (trader) => { - // Show feedback - setSnackbar({ - isVisible: true, - message: `Removed ${trader.name}`, - status: "neutral", - }); - // Refresh the traders list from API - refreshList(); - }; - return (
@@ -181,11 +149,7 @@ const CopierDashboard = () => { name: trader.name || `Trader ${trader.loginid}`, token: trader.token, }} - onCopyClick={handleCopyClick} onStopCopy={handleStopCopy} - onRemoveTrader={handleRemoveTrader} - isCopied={copiedTrader?.id === trader.loginid} - hasFailed={failedCopyTrader?.id === trader.loginid} /> ))}