Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor websocket logic #28

Merged
merged 13 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
26 changes: 18 additions & 8 deletions src/components/AddTraderForm.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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",
});
Expand All @@ -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,
});
};
Expand Down
35 changes: 29 additions & 6 deletions src/components/CopierDashboard.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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);
});
};

Expand Down Expand Up @@ -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}
/>
))}
</div>
Expand Down
45 changes: 14 additions & 31 deletions src/components/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -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 [userType, setUserType] = useState("trader");

const items = [
{ label: "Trader", value: "trader" },
{ label: "Copier", value: "copier" },
];
const { isConnected } = useWebSocket();
const [userType, setUserType] = useState("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 <div>Loading...</div>;
}

Expand All @@ -46,21 +29,21 @@ const Dashboard = () => {
<div className="flex justify-center gap-4 mb-8">
<Button
variant={
userType === "trader" || userType === null
? "primary"
: "secondary"
userType === "copier" ? "primary" : "secondary"
}
onClick={handleBecomeTrader}
onClick={handleBecomeCopier}
>
Trader
Copier
</Button>
<Button
variant={
userType === "copier" ? "primary" : "secondary"
userType === "trader" || userType === null
? "primary"
: "secondary"
}
onClick={handleBecomeCopier}
onClick={handleBecomeTrader}
>
Copier
Trader
</Button>
</div>

Expand All @@ -70,7 +53,7 @@ const Dashboard = () => {
) : userType === "copier" ? (
<CopierDashboard />
) : (
<TraderDashboard />
<CopierDashboard />
)}
</div>
</div>
Expand Down
50 changes: 50 additions & 0 deletions src/hooks/useAuthorize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useEffect } from 'react';
import useWebSocket from './useWebSocket';
import useDerivAccounts from './useDerivAccounts';

// Singleton state
let isAuthorizedGlobal = false;
let authErrorGlobal = null;

const useAuthorize = () => {
const { defaultAccount, clearAccounts } = useDerivAccounts();
const { isConnected, sendMessage, close } = useWebSocket();

// 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;
71 changes: 49 additions & 22 deletions src/hooks/useCopyTradersList.js
Original file line number Diff line number Diff line change
@@ -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
}
}

Expand Down
Loading
Loading