From ce595f51ef360c4cea5383fd827320f56d919e82 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 4 Mar 2025 13:58:45 +0400 Subject: [PATCH 01/17] feat: created product service for getting products types --- .../rest/product/__tests__/service.test.ts | 47 ++++++++++++++++ src/services/api/rest/product/service.ts | 14 +++++ src/services/api/rest/types.ts | 53 +++++++++++-------- src/services/index.ts | 4 +- 4 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 src/services/api/rest/product/__tests__/service.test.ts create mode 100644 src/services/api/rest/product/service.ts diff --git a/src/services/api/rest/product/__tests__/service.test.ts b/src/services/api/rest/product/__tests__/service.test.ts new file mode 100644 index 0000000..c95a257 --- /dev/null +++ b/src/services/api/rest/product/__tests__/service.test.ts @@ -0,0 +1,47 @@ +import { getProducts } from "../service"; +import { apiClient } from "../../../axios_interceptor"; +import { ProductsResponse } from "../../types"; + +// Mock the axios interceptor +jest.mock("../../../axios_interceptor", () => ({ + apiClient: { + get: jest.fn(), + }, +})); + +describe("getProducts", () => { + beforeEach(() => { + (apiClient.get as jest.Mock).mockClear(); + }); + + it("fetches products successfully", async () => { + // Mock response based on trade types in the system + const mockResponse: ProductsResponse = { + data: { + products: [ + { id: "rise_fall", display_name: "Rise/Fall" }, + { id: "high_low", display_name: "Higher/Lower" }, + { id: "touch", display_name: "Touch/No Touch" }, + { id: "multiplier", display_name: "Multiplier" }, + ], + }, + }; + + (apiClient.get as jest.Mock).mockResolvedValueOnce({ + data: mockResponse, + }); + + const response = await getProducts(); + + expect(apiClient.get).toHaveBeenCalledWith("/v1/market/products"); + expect(response).toEqual(mockResponse); + }); + + it("handles API errors", async () => { + const mockError = new Error("API Error"); + (apiClient.get as jest.Mock).mockRejectedValueOnce(mockError); + + await expect(getProducts()).rejects.toThrow("API Error"); + expect(apiClient.get).toHaveBeenCalledWith("/v1/market/products"); + }); +}); diff --git a/src/services/api/rest/product/service.ts b/src/services/api/rest/product/service.ts new file mode 100644 index 0000000..8e7552e --- /dev/null +++ b/src/services/api/rest/product/service.ts @@ -0,0 +1,14 @@ +import { apiClient } from "../../axios_interceptor"; +import { ProductsResponse } from "../types"; + +/** + * Fetches available products + * @returns Promise resolving to list of available products + * @throws AxiosError with ErrorResponse data on validation or server errors + */ +export const getProducts = async (): Promise => { + const response = await apiClient.get( + "/v1/market/products" + ); + return response.data; +}; diff --git a/src/services/api/rest/types.ts b/src/services/api/rest/types.ts index 1e65ed9..1912b72 100644 --- a/src/services/api/rest/types.ts +++ b/src/services/api/rest/types.ts @@ -1,42 +1,51 @@ export interface Context { - app_id: string; - account_type?: string; + app_id: string; + account_type?: string; } export interface AvailableInstrumentsRequest { - instrument: string; - context: Context; + instrument: string; + context: Context; } export interface MarketGroup { - instruments: string[]; - market_name: string; + instruments: string[]; + market_name: string; } export interface AvailableInstrumentsResponse { - performance: string; - result: MarketGroup[]; + performance: string; + result: MarketGroup[]; } export interface ErrorResponse { - error: { - code: string; - message: string; - }; + error: { + code: string; + message: string; + }; } export interface BuyRequest { - price: number; - instrument: string; - duration: string; - trade_type: string; - currency: string; - payout: number; - strike: string; + price: number; + instrument: string; + duration: string; + trade_type: string; + currency: string; + payout: number; + strike: string; } export interface BuyResponse { - contract_id: string; - price: number; - trade_type: string; + contract_id: string; + price: number; + trade_type: string; +} + +export interface ProductsResponse { + data: { + products: Array<{ + id: string; + display_name: string; + }>; + }; } diff --git a/src/services/index.ts b/src/services/index.ts index 62e59bf..215d7d2 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,2 +1,2 @@ -export { apiClient } from './api/axios_interceptor'; -export * from './api/websocket/types'; +export { apiClient } from "./api/axios_interceptor"; +export * from "./api/websocket/types"; From 47520407b4c55af8a8ee7c8f1f8c2777c0ab534d Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 4 Mar 2025 16:18:46 +0400 Subject: [PATCH 02/17] styles: basic UI changed --- src/layouts/MainLayout/Header.tsx | 69 --------- src/layouts/MainLayout/MainLayout.tsx | 159 +++++++++++--------- src/layouts/MainLayout/ResponsiveHeader.tsx | 64 ++++++++ src/screens/TradePage/TradePage.tsx | 137 +++++++++-------- 4 files changed, 224 insertions(+), 205 deletions(-) delete mode 100644 src/layouts/MainLayout/Header.tsx create mode 100644 src/layouts/MainLayout/ResponsiveHeader.tsx diff --git a/src/layouts/MainLayout/Header.tsx b/src/layouts/MainLayout/Header.tsx deleted file mode 100644 index 232b04f..0000000 --- a/src/layouts/MainLayout/Header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; -import { useOrientationStore } from "@/stores/orientationStore"; -import { useClientStore } from "@/stores/clientStore"; -// import { AccountSwitcher } from "@/components/AccountSwitcher"; -import { BalanceDisplay } from "@/components/BalanceDisplay"; - -interface HeaderProps { - className?: string; - onDeposit?: () => void; - depositLabel?: string; - loginUrl?: string; -} - -export const Header: React.FC = ({ - className = "", - onDeposit, - depositLabel = "Deposit", - loginUrl = "/login", -}) => { - const { isLandscape } = useOrientationStore(); - const { isLoggedIn } = useClientStore(); - const showLogo = isLandscape || !isLoggedIn; - - return ( - - ); -}; diff --git a/src/layouts/MainLayout/MainLayout.tsx b/src/layouts/MainLayout/MainLayout.tsx index a81fd83..f9d8f30 100644 --- a/src/layouts/MainLayout/MainLayout.tsx +++ b/src/layouts/MainLayout/MainLayout.tsx @@ -1,85 +1,98 @@ -import React, { useEffect } from "react" -import { useDeviceDetection } from "@/hooks/useDeviceDetection" -import { useOrientationStore } from "@/stores/orientationStore" -import { useHeaderStore } from "@/stores/headerStore" -import { useBottomNavStore } from "@/stores/bottomNavStore" -import { useMainLayoutStore } from "@/stores/mainLayoutStore" -import { Footer } from "./Footer" -import { Header } from "./Header" -import { SideNav } from "@/components/SideNav" -import { Sidebar, MenuContent, PositionsContent } from "@/components/Sidebar" +import React, { useEffect } from "react"; +import { useDeviceDetection } from "@/hooks/useDeviceDetection"; +import { useOrientationStore } from "@/stores/orientationStore"; +import { useHeaderStore } from "@/stores/headerStore"; +import { useBottomNavStore } from "@/stores/bottomNavStore"; +import { useMainLayoutStore } from "@/stores/mainLayoutStore"; +import { Footer } from "./Footer"; +import { ResponsiveHeader } from "./ResponsiveHeader"; +import { SideNav } from "@/components/SideNav"; +import { Sidebar, MenuContent, PositionsContent } from "@/components/Sidebar"; interface MainLayoutProps { - children: React.ReactNode + children: React.ReactNode; } export const MainLayout: React.FC = ({ children }) => { - const { isMobile } = useDeviceDetection() - const { isLandscape, setIsLandscape } = useOrientationStore() - const { activeSidebar, setSidebar } = useMainLayoutStore() - const isHeaderVisible = useHeaderStore((state) => state.isVisible) - const isBottomNavVisible = useBottomNavStore((state) => state.isVisible) + const { isMobile } = useDeviceDetection(); + const { isLandscape, setIsLandscape } = useOrientationStore(); + const { activeSidebar, setSidebar } = useMainLayoutStore(); + const isHeaderVisible = useHeaderStore((state) => state.isVisible); + const isBottomNavVisible = useBottomNavStore((state) => state.isVisible); - useEffect(() => { - const handleOrientationChange = () => { - const isLandscapeMode = window.matchMedia - ? window.matchMedia("(orientation: landscape)").matches - : window.innerWidth > window.innerHeight - setIsLandscape(isLandscapeMode) - } + useEffect(() => { + const handleOrientationChange = () => { + const isLandscapeMode = window.matchMedia + ? window.matchMedia("(orientation: landscape)").matches + : window.innerWidth > window.innerHeight; + setIsLandscape(isLandscapeMode); + }; - handleOrientationChange() - window.addEventListener("orientationchange", handleOrientationChange) - window.addEventListener("resize", handleOrientationChange) + handleOrientationChange(); + window.addEventListener("orientationchange", handleOrientationChange); + window.addEventListener("resize", handleOrientationChange); - return () => { - window.removeEventListener("orientationchange", handleOrientationChange) - window.removeEventListener("resize", handleOrientationChange) - } - }, [isMobile, isLandscape]) + return () => { + window.removeEventListener( + "orientationchange", + handleOrientationChange + ); + window.removeEventListener("resize", handleOrientationChange); + }; + }, [isMobile, isLandscape]); - const shouldEnableScrolling = isLandscape && window.innerHeight < 500; + const shouldEnableScrolling = isLandscape && window.innerHeight < 500; - return ( -
- {isHeaderVisible &&
} -
- {isLandscape && } -
- {isLandscape ? ( -
-
- setSidebar(null)} - title="Positions" - > - - - setSidebar(null)} - title="Menu" - > - - -
-
- {children} -
+ return ( +
+ {isMobile && ( + + )} +
+ {isLandscape && } +
+ {isLandscape ? ( +
+
+ setSidebar(null)} + title="Positions" + > + + + setSidebar(null)} + title="Menu" + > + + +
+
+ {children} +
+
+ ) : ( +
+ {children} + {isBottomNavVisible && ( +
+ )} +
+ )} +
- ) : ( -
- {children} - {isBottomNavVisible && ( -
- )} -
- )}
-
-
- ) -} + ); +}; diff --git a/src/layouts/MainLayout/ResponsiveHeader.tsx b/src/layouts/MainLayout/ResponsiveHeader.tsx new file mode 100644 index 0000000..43e5262 --- /dev/null +++ b/src/layouts/MainLayout/ResponsiveHeader.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { useOrientationStore } from "@/stores/orientationStore"; +import { useClientStore } from "@/stores/clientStore"; +// import { AccountSwitcher } from "@/components/AccountSwitcher"; +import { BalanceDisplay } from "@/components/BalanceDisplay"; + +interface HeaderProps { + className?: string; + onDeposit?: () => void; + depositLabel?: string; + loginUrl?: string; +} + +export const ResponsiveHeader: React.FC = ({ + className = "", + onDeposit, + depositLabel = "Deposit", + loginUrl = "/login", +}) => { + const { isLoggedIn } = useClientStore(); + const showLogo = !isLoggedIn; + + return ( + + ); +}; diff --git a/src/screens/TradePage/TradePage.tsx b/src/screens/TradePage/TradePage.tsx index 09ccc9d..4ef5b97 100644 --- a/src/screens/TradePage/TradePage.tsx +++ b/src/screens/TradePage/TradePage.tsx @@ -11,76 +11,87 @@ import { useMarketStore } from "@/stores/marketStore"; import { MarketInfo } from "@/components/MarketInfo"; const Chart = lazy(() => - import("@/components/Chart").then((module) => ({ - default: module.Chart, - })) + import("@/components/Chart").then((module) => ({ + default: module.Chart, + })) ); export const TradePage: React.FC = () => { - const { isLandscape } = useOrientationStore(); - const { setBottomSheet } = useBottomSheetStore(); - const { isMobile } = useDeviceDetection(); - const selectedMarket = useMarketStore((state) => state.selectedMarket); - const { setOverlaySidebar, activeSidebar } = useMainLayoutStore(); + const { isLandscape } = useOrientationStore(); + const { setBottomSheet } = useBottomSheetStore(); + const { isMobile } = useDeviceDetection(); + const selectedMarket = useMarketStore((state) => state.selectedMarket); + const { setOverlaySidebar, activeSidebar } = useMainLayoutStore(); - const handleMarketSelect = React.useCallback(() => { - if (isMobile) { - setBottomSheet(true, "market-info", "80%"); - } else { - setOverlaySidebar(true, "market-list"); - } - }, [isMobile, setBottomSheet, setOverlaySidebar]); + const handleMarketSelect = React.useCallback(() => { + if (isMobile) { + setBottomSheet(true, "market-info", "80%"); + } else { + setOverlaySidebar(true, "market-list"); + } + }, [isMobile, setBottomSheet, setOverlaySidebar]); - return ( -
-
-
- {isLandscape && ( -
- -
- )} - {!isLandscape && ( - - )} -
- Loading...
}> - - -
+ return ( +
+
+
+ {isLandscape && ( +
+ +
+ )} + {!isLandscape && ( + + )} +
+ Loading...
}> + + +
- {!isLandscape && ( - Loading...
}> - - - )} -
-
+ {!isLandscape && ( + Loading...
}> + + + )} +
+
- + - {!isMobile && } + {!isMobile && } - - - ); + + + ); }; From 5554ad4c9e89afb21a7c57b4c8188a1236c94e75 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 4 Mar 2025 16:27:16 +0400 Subject: [PATCH 03/17] feat: added the TradeTypeList and TradeTypesListController --- src/components/TradeTypes/TradeTypesList.tsx | 32 +++++++++ src/components/TradeTypes/index.ts | 1 + src/screens/TradePage/TradePage.tsx | 2 + .../components/TradeTypesListController.tsx | 67 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/components/TradeTypes/TradeTypesList.tsx create mode 100644 src/components/TradeTypes/index.ts create mode 100644 src/screens/TradePage/components/TradeTypesListController.tsx diff --git a/src/components/TradeTypes/TradeTypesList.tsx b/src/components/TradeTypes/TradeTypesList.tsx new file mode 100644 index 0000000..3037267 --- /dev/null +++ b/src/components/TradeTypes/TradeTypesList.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { Chip } from "@/components/ui/chip"; + +interface TradeTypesListProps { + selectedProductId: string; + products: Array<{ + id: string; + display_name: string; + }>; + onProductSelect: (productId: string) => void; +} + +export const TradeTypesList: React.FC = ({ + selectedProductId, + products, + onProductSelect, +}) => { + return ( +
+ {products.map((product) => ( +
+ onProductSelect(product.id)} + > + {product.display_name} + +
+ ))} +
+ ); +}; diff --git a/src/components/TradeTypes/index.ts b/src/components/TradeTypes/index.ts new file mode 100644 index 0000000..430246a --- /dev/null +++ b/src/components/TradeTypes/index.ts @@ -0,0 +1 @@ +export { TradeTypesList } from "./TradeTypesList"; diff --git a/src/screens/TradePage/TradePage.tsx b/src/screens/TradePage/TradePage.tsx index 4ef5b97..19f21a5 100644 --- a/src/screens/TradePage/TradePage.tsx +++ b/src/screens/TradePage/TradePage.tsx @@ -9,6 +9,7 @@ import { useDeviceDetection } from "@/hooks/useDeviceDetection"; import { useMainLayoutStore } from "@/stores/mainLayoutStore"; import { useMarketStore } from "@/stores/marketStore"; import { MarketInfo } from "@/components/MarketInfo"; +import { TradeTypesListController } from "./components/TradeTypesListController"; const Chart = lazy(() => import("@/components/Chart").then((module) => ({ @@ -63,6 +64,7 @@ export const TradePage: React.FC = () => { /> )} + {!isLandscape && ( { + // Get trade_type and setTradeType from tradeStore + const trade_type = useTradeStore((state) => state.trade_type); + const setTradeType = useTradeStore((state) => state.setTradeType); + + // State for trade types + const [tradeTypes, setTradeTypes] = useState< + Array<{ id: string; display_name: string }> + >([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch trade types on component mount + useEffect(() => { + const fetchTradeTypes = async () => { + try { + setIsLoading(true); + const response = await getProducts(); + setTradeTypes(response.data.products); + setError(null); + } catch (err) { + setError("Failed to load trade types"); + console.error(err); + // Fallback to default trade types if API fails + setTradeTypes([ + { id: "rise_fall", display_name: "Rise/Fall" }, + { id: "high_low", display_name: "Higher/Lower" }, + { id: "touch", display_name: "Touch/No Touch" }, + { id: "multiplier", display_name: "Multiplier" }, + ]); + } finally { + setIsLoading(false); + } + }; + + fetchTradeTypes(); + }, []); + + // Handle trade type selection + const handleTradeTypeSelect = (tradeTypeId: string) => { + setTradeType(tradeTypeId as TradeType); + }; + + // Show loading state or error + if (isLoading) { + return
Loading trade types...
; + } + + if (error && tradeTypes.length === 0) { + return
Error: {error}
; + } + + // Render TradeTypesList component + return ( + + ); +}; From d5945f87e861a4d62edc427527160eb3f3b4cf89 Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 4 Mar 2025 16:37:33 +0400 Subject: [PATCH 04/17] chore: remove unused imports --- src/layouts/MainLayout/MainLayout.tsx | 2 -- src/layouts/MainLayout/ResponsiveHeader.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/src/layouts/MainLayout/MainLayout.tsx b/src/layouts/MainLayout/MainLayout.tsx index f9d8f30..da16ee0 100644 --- a/src/layouts/MainLayout/MainLayout.tsx +++ b/src/layouts/MainLayout/MainLayout.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from "react"; import { useDeviceDetection } from "@/hooks/useDeviceDetection"; import { useOrientationStore } from "@/stores/orientationStore"; -import { useHeaderStore } from "@/stores/headerStore"; import { useBottomNavStore } from "@/stores/bottomNavStore"; import { useMainLayoutStore } from "@/stores/mainLayoutStore"; import { Footer } from "./Footer"; @@ -17,7 +16,6 @@ export const MainLayout: React.FC = ({ children }) => { const { isMobile } = useDeviceDetection(); const { isLandscape, setIsLandscape } = useOrientationStore(); const { activeSidebar, setSidebar } = useMainLayoutStore(); - const isHeaderVisible = useHeaderStore((state) => state.isVisible); const isBottomNavVisible = useBottomNavStore((state) => state.isVisible); useEffect(() => { diff --git a/src/layouts/MainLayout/ResponsiveHeader.tsx b/src/layouts/MainLayout/ResponsiveHeader.tsx index 43e5262..8dadce4 100644 --- a/src/layouts/MainLayout/ResponsiveHeader.tsx +++ b/src/layouts/MainLayout/ResponsiveHeader.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { useOrientationStore } from "@/stores/orientationStore"; import { useClientStore } from "@/stores/clientStore"; // import { AccountSwitcher } from "@/components/AccountSwitcher"; import { BalanceDisplay } from "@/components/BalanceDisplay"; From 84f499d4ed2978c380dcbc5de699a0ca0a45ec3c Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Tue, 4 Mar 2025 16:49:27 +0400 Subject: [PATCH 05/17] styles: restructure TradePage to position TradeTypesListController --- src/screens/TradePage/TradePage.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/screens/TradePage/TradePage.tsx b/src/screens/TradePage/TradePage.tsx index 19f21a5..d6c1f0a 100644 --- a/src/screens/TradePage/TradePage.tsx +++ b/src/screens/TradePage/TradePage.tsx @@ -39,12 +39,13 @@ export const TradePage: React.FC = () => { } flex-1 py-4`} data-testid="trade-page" > -
-
+
+ +
{isLandscape && (
{ />
)} - {!isLandscape && ( Date: Wed, 5 Mar 2025 10:59:46 +0400 Subject: [PATCH 06/17] refactor: use TabList for TradeTypesListController + minor UI changes --- public/favicon.ico | Bin 15406 -> 4414 bytes public/logo.png | Bin 569 -> 0 bytes public/logo.svg | 12 + rsbuild.config.ts | 128 ++++---- src/components/MarketInfo/MarketInfo.tsx | 142 ++++---- src/components/SideNav/SideNav.tsx | 133 +++++--- src/components/TradeTypes/TradeTypesList.tsx | 32 -- src/components/TradeTypes/index.ts | 1 - src/components/ui/tab-list.tsx | 117 +++---- src/config/constants.ts | 6 +- src/config/tradeTypes.ts | 305 +++++++++--------- src/layouts/MainLayout/MainLayout.tsx | 2 +- src/layouts/MainLayout/ResponsiveHeader.tsx | 2 +- .../components/TradeTypesListController.tsx | 32 +- 14 files changed, 481 insertions(+), 431 deletions(-) delete mode 100644 public/logo.png create mode 100644 public/logo.svg delete mode 100644 src/components/TradeTypes/TradeTypesList.tsx delete mode 100644 src/components/TradeTypes/index.ts diff --git a/public/favicon.ico b/public/favicon.ico index d7eea1a074cc6b67c021f08d7f6b69ee21434e73..a35a9103608585b4050e2b652d264f1b295b5910 100644 GIT binary patch literal 4414 zcmdUyJxjw-6oy|xL|wEFx>gXZf55q$h&T!ky4fG$;^q(N4{&i3!NJw94n?tkG}%PN zrGt}$xL6c>d~Zx4h9vjWTynAHBsV8H?{jiu`YKQPtq9FM^-i9`gn-& zqU^tA53h{bX1$CK%1n!sd(1J1I`zK{a(U!4i)mVU{LEv4IoufJ(B(JT%)?5HW&0Z= z|HvSR9&@dw>E!XNwomO7gB-qDXRCQwX|ZhY6XbWaIe2)J7G~3Q^7#2~OIp6_uO2S? z967`YWAWay>zZ}{qUHP8u*Hk(ko}?MdsvCb*}5jX>y+=l{@u1e z*Zt4&_@mk}+n=-XH{d&-e{A*T;9bn;--bu+)k f-+yqfcE0}#_n$S^cj5cbr2bm;{yQm$|Lpq#EfWm7 literal 15406 zcmeI2e~ev46~_-PAlizp%KD=S3kobh5(|P_QPcP%Y>H_8fj_E={GmpuCIqD>Lc6UI z_yeK|rjP>K*aAXDFf~>om?hY(EwsJwM_bx%%R-A(L<(A4KuWKl@65Yz@4N5b_wMd{ zyHCu%^oH~R&3GiO%bpK3O6 zoLLWT=t2))Pm${vhBhfI+lRt}t$=offelgiLyN+#b|UYOXvQcUvSaAiO`o4Y2cQr- z6c%k~ShA1i;UO8}W;+@_->2`36&~XAIQ26l9!^%tJ`rxQUPndy81EPn|8wt)b{qW; zS9qx081Aw+sRhqhK>H!d5ZxR0+cA~yu3$V)2m4LXt&R$IGZ@}m>A#Ua!WDw`74-XN zyl?aj&m)B^Mw49sz@D%6m;92=iTdOz>HA!Z{U40=f0gI&MPnPu4_Db_^qpEml`&>K zK7!sGpt7Ta-9=qj)K?`uFW}v#YMa2;)8-ua4tom!W>ac-BQNS7ZnHPiZ%quN=5KYl z!QK$HNh_s;<`_9Q(EdM_{)gceP(Y%$rk(SK(%*iEB0oqi`j(+$mrELk*^i+znl4e*%IW(_YBF7NVtS=5%Zz#zxu28*M3)I zKI^HEy33+{vDz{A;CO7dGvL1kN*e>=Q2iFx<{5Ea*hAk2ratIN==l_S8u_<|n~dls z`$xZN@Lx!|gK}>MJ;pfA*WM9vLjD)&Gf4Y1-16_c>2tCCNz^B;tc+t9SbJ$dHo@H)43gW z$ktx^Qil8&cz-QpL^L}#zlv5TRzx<~BMcN!_FO_dlY***E>StKBm}* z93M;6mV2?mAF{5Gi?7?bY&|>K1^W~{R)G1w47w9u->C6_#3!t;9{Syu+NWT@;oZ;h zzq1qvCFMHV(!Ff&qTSu_kT0v(L%u}5Ik3;f{a>a3WT#)&xM@rq#B=VQe6){w(&kZT zu!HEn%HfRpzchVDQpcQ3hUe+XzQ@T(Uq^>{w-x!H7uJ#RZ`4+H#N3^X?Azfj`$^MX z``RwreKq{EO>bmFjbz2&Il0)4*|N z1;YY|NqP68JHMsv|2k7B4~}LerUPRK_9^Hx3)~B#hr#qLb;($$Rww^n=dJZl2hGDc*;3LN zoo`Ye-^b6&<7r+08_KdJuZv$FI-3rlZ=Z_?V|N2dIb;3b`6%uVz;n67G!k2qp9<{j ztcPyhr;Ih$H2!WLq8zt;rS5IK4vpu*+tOaz-@!b5nf>LRtcj!3WJGmN7I#FHN9827 zZGN|?_*Ax3viE^UIy{07(fv;C8i=pyg1t;z#nQi`oQEDYcT^GXsI1D+ew2rVCzfxF z_+*uizjA~HTMo%@Ho_jg)A@1;?6;u5LW;Vd8~12@3U)Q^b%)ZJ{5|b=i06;5W0+Cl z_`BW3y&HHumyI%ew18!=yY#2Eg z(tjl+KK1#G^tx?~|3YM*liU}mI}M)s{e!n%`G)z(pOFeQqte~W%Ab_&v-H(@P_~qZ zZUpD0v^~2Hf7w1qz2d3;P-DL!|6H_xfL-==9^*MR18Qcq(?PLXz#hAq^^k}3eL~sJ z4cFD5eZg}TydD619(&Xtq`dTZ!}azSY5$e=x5Fzx-Kq{5~(E z^>%@LNhk0oWmd57V@w;zhkg#8qWx00zp+mzd3EBy8~1$JWm^;Y+dN7C2ln#^>|#55 zyf)Xb&({o~L!Ovc_m&Uh|L6VB_|JfRQsDN0=J!ni z|7JF%_(;;Ry)z!H-&)B~R?(Nvab#ClOan@^92j?$!&I-7X4D3!w-(c#@-uRsA zZGVuqx*wRu8q>EKqdM!gqJ#6-lJ9fT%BSba`}mwsV82A)+2GIZ-&(;M_0^sR&MxN% zWHbKTSmj%?zh{chck!H?Vw4f?FA;`3rLzrvC!sqqoyT<#ru$-juOivr56j2vY^OVb zESpii)~V)3ZCBDaSI#pJ&a5`hmu(`is(j7{cMtbraL+~l6=1#>8sa(6I#o_U`@wQ> zU&-@@{rr0y!|<-xXb6QQuxD7bbg%(?R}gFI5gnc5)YemQr5Lyt!PkY%*)U$D+RG z&Z}r&0FUyJ`j%ZW#_|L$#tv$8 zgR0Xz?FYKE*1d+mYshtv&~DsW{G45-* diff --git a/public/logo.png b/public/logo.png deleted file mode 100644 index 2241778d8c67149c638adf5f256a91ddbccfbe71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmV-90>=G`P)N5%XF zdTt660l&qh+)nrlcM5jJ5gBqcDwqk_72@y)O+*OKEMjgeU@K&yiiYE;?JS9%06|F- ziBXhC7;_|+_5vhB910v}#y)Bm(U(1O0ZT~A5grsw5&9|*+X(pZC5_z(FPqRcqNEkK zgD8)e!VCDM8NWP5=nG^{GrLW_SSgsf3u*Kfr4p`>p)8Zn%Sl32qHkUzM6f~Qh;WMYci!J{=E?SQe4nHSgj^3 zxfG9~fxZ-1m*HW;Gn7BnRRcL$f4J{*#1Nm12VrXK2LuI;f<=oV=ZFq%>wq^TGX?X< zH`ogBCL@jVWbm+|?F5u0gKPZYbl22a*>V|t5apWMa#`;S-tFE=#eQg2aKZ;w_tc|V z7E&yOt~}Rk9|_F=IwQ6@oTiX)JKW;<8L`3%hf^DzaXkM91u>FiH}kV*00000NkvXX Hu0mjf3>xE- diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..a9a98ad --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/rsbuild.config.ts b/rsbuild.config.ts index ad868c0..21346c6 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -7,71 +7,73 @@ import path from "path"; dotenv.config(); export default defineConfig({ - plugins: [pluginReact(), pluginBasicSsl()], - html: { - template: "./index.html", - title: "Champion Trader", - favicon: "public/favicon.ico", - }, - source: { - define: { - "process.env.RSBUILD_WS_URL": JSON.stringify(process.env.RSBUILD_WS_URL), - "process.env.RSBUILD_WS_PUBLIC_PATH": JSON.stringify( - process.env.RSBUILD_WS_PUBLIC_PATH - ), - "process.env.RSBUILD_WS_PROTECTED_PATH": JSON.stringify( - process.env.RSBUILD_WS_PROTECTED_PATH - ), - "process.env.RSBUILD_REST_URL": JSON.stringify( - process.env.RSBUILD_REST_URL - ), - "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), - "process.env.RSBUILD_SSE_PUBLIC_PATH": JSON.stringify( - process.env.RSBUILD_SSE_PUBLIC_PATH - ), - "process.env.RSBUILD_SSE_PROTECTED_PATH": JSON.stringify( - process.env.RSBUILD_SSE_PROTECTED_PATH - ), + plugins: [pluginReact(), pluginBasicSsl()], + html: { + template: "./index.html", + title: "Champion Trader", + favicon: "public/favicon.ico", }, - alias: { - "@": "./src", + source: { + define: { + "process.env.RSBUILD_WS_URL": JSON.stringify( + process.env.RSBUILD_WS_URL + ), + "process.env.RSBUILD_WS_PUBLIC_PATH": JSON.stringify( + process.env.RSBUILD_WS_PUBLIC_PATH + ), + "process.env.RSBUILD_WS_PROTECTED_PATH": JSON.stringify( + process.env.RSBUILD_WS_PROTECTED_PATH + ), + "process.env.RSBUILD_REST_URL": JSON.stringify( + process.env.RSBUILD_REST_URL + ), + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), + "process.env.RSBUILD_SSE_PUBLIC_PATH": JSON.stringify( + process.env.RSBUILD_SSE_PUBLIC_PATH + ), + "process.env.RSBUILD_SSE_PROTECTED_PATH": JSON.stringify( + process.env.RSBUILD_SSE_PROTECTED_PATH + ), + }, + alias: { + "@": "./src", + }, }, - }, - server: { - port: 4113, - host: "localhost", - strictPort: true, - }, - output: { - copy: [ - { - from: path.resolve( - __dirname, - "node_modules/@deriv-com/smartcharts-champion/dist" - ), - to: "js/smartcharts/", - globOptions: { - ignore: ["**/*.LICENSE.txt"] - } - }, - { - from: path.resolve( - __dirname, - "node_modules/@deriv-com/smartcharts-champion/dist/chart/assets" - ), - to: "assets", - }, - ], - filename: { - js: "[name].[hash:8].js", - css: "[name].[hash:8].css", - assets: "assets/[name].[hash:8][ext]" + server: { + port: 4113, + host: "localhost", + strictPort: true, }, - distPath: { - js: "js", - css: "css", - html: "" + output: { + copy: [ + { + from: path.resolve( + __dirname, + "node_modules/@deriv-com/smartcharts-champion/dist" + ), + to: "js/smartcharts/", + globOptions: { + ignore: ["**/*.LICENSE.txt"], + }, + }, + { + from: path.resolve( + __dirname, + "node_modules/@deriv-com/smartcharts-champion/dist/chart/assets" + ), + to: "assets", + }, + ], + filename: { + js: "[name].[hash:8].js", + css: "[name].[hash:8].css", + assets: "assets/[name].[hash:8][ext]", + }, + distPath: { + js: "js", + css: "css", + html: "", + }, + cleanDistPath: true, }, - cleanDistPath: true - }, }); diff --git a/src/components/MarketInfo/MarketInfo.tsx b/src/components/MarketInfo/MarketInfo.tsx index 5fd9bbd..194ca4d 100644 --- a/src/components/MarketInfo/MarketInfo.tsx +++ b/src/components/MarketInfo/MarketInfo.tsx @@ -1,76 +1,86 @@ -import React from "react" -import { MarketIcon } from "@/components/MarketSelector/MarketIcon" -import { useMarketStore } from "@/stores/marketStore" -import { ChevronDown } from "lucide-react" +import React from "react"; +import { MarketIcon } from "@/components/MarketSelector/MarketIcon"; +import { useMarketStore } from "@/stores/marketStore"; +import { ChevronDown } from "lucide-react"; interface MarketInfoProps { - title: string - subtitle: string - onClick?: () => void - isMobile?: boolean + title: string; + subtitle: string; + onClick?: () => void; + isMobile?: boolean; } export const MarketInfo: React.FC = ({ - title, - subtitle, - onClick, - isMobile = false, + title, + subtitle, + onClick, + isMobile = false, }) => { - const selectedMarket = useMarketStore((state) => state.selectedMarket) + const selectedMarket = useMarketStore((state) => state.selectedMarket); - if (isMobile) { - return ( -
-
- {selectedMarket && ( -
- -
- )} -
-
-
{title}
- + if (isMobile) { + return ( +
+
+ {selectedMarket && ( +
+ +
+ )} +
+
+
+ {title} +
+ +
+
+ {subtitle} +
+
+
-
{subtitle}
-
-
-
- ) - } + ); + } - return ( -
-
- {selectedMarket && ( -
- -
- )} -
-
-
{title}
- -
-
{subtitle}
+ return ( +
+
+ {selectedMarket && ( +
+ +
+ )} +
+
+
+ {title} +
+ +
+
+ {subtitle} +
+
+
-
-
- ) -} + ); +}; diff --git a/src/components/SideNav/SideNav.tsx b/src/components/SideNav/SideNav.tsx index e35cd0d..bc4dbc7 100644 --- a/src/components/SideNav/SideNav.tsx +++ b/src/components/SideNav/SideNav.tsx @@ -6,51 +6,94 @@ import { useOrientationStore } from "@/stores/orientationStore"; import { useMainLayoutStore } from "@/stores/mainLayoutStore"; export const SideNav: React.FC = () => { - const navigate = useNavigate(); - const location = useLocation(); - const { isLoggedIn } = useClientStore(); - const { isLandscape } = useOrientationStore(); - const { activeSidebar, toggleSidebar, isSideNavVisible } = useMainLayoutStore(); + const navigate = useNavigate(); + const location = useLocation(); + const { isLoggedIn } = useClientStore(); + const { isLandscape } = useOrientationStore(); + const { activeSidebar, toggleSidebar, isSideNavVisible } = + useMainLayoutStore(); - return ( - - ); +
+ + Champion Trader Logo + + {isLoggedIn && ( + <> + + + )} + +
+ + ); }; diff --git a/src/components/TradeTypes/TradeTypesList.tsx b/src/components/TradeTypes/TradeTypesList.tsx deleted file mode 100644 index 3037267..0000000 --- a/src/components/TradeTypes/TradeTypesList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import { Chip } from "@/components/ui/chip"; - -interface TradeTypesListProps { - selectedProductId: string; - products: Array<{ - id: string; - display_name: string; - }>; - onProductSelect: (productId: string) => void; -} - -export const TradeTypesList: React.FC = ({ - selectedProductId, - products, - onProductSelect, -}) => { - return ( -
- {products.map((product) => ( -
- onProductSelect(product.id)} - > - {product.display_name} - -
- ))} -
- ); -}; diff --git a/src/components/TradeTypes/index.ts b/src/components/TradeTypes/index.ts deleted file mode 100644 index 430246a..0000000 --- a/src/components/TradeTypes/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TradeTypesList } from "./TradeTypesList"; diff --git a/src/components/ui/tab-list.tsx b/src/components/ui/tab-list.tsx index 460edd5..a37fa93 100644 --- a/src/components/ui/tab-list.tsx +++ b/src/components/ui/tab-list.tsx @@ -1,88 +1,89 @@ import React from "react"; export interface Tab { - label: string; - value: string; + label: string; + value: string; } interface BaseTabListProps { - tabs: Tab[]; - selectedValue: string; - onSelect: (value: string) => void; + tabs: Tab[]; + selectedValue: string; + onSelect: (value: string) => void; } interface TabListProps extends BaseTabListProps { - variant: "chip" | "vertical"; + variant: "chip" | "vertical"; } const ChipTabList: React.FC = ({ - tabs, - selectedValue, - onSelect, + tabs, + selectedValue, + onSelect, }) => { - return ( -
-
- {tabs.map(({ label, value }) => ( -
- -
- ))} -
-
- ); + > + {label} + +
+ ))} +
+
+ ); }; const VerticalTabList: React.FC = ({ - tabs, - selectedValue, - onSelect, + tabs, + selectedValue, + onSelect, }) => { - return ( -
- {tabs.map(({ label, value }) => ( - - ))} -
- ); + > + {label} + + ))} +
+ ); }; export const TabList: React.FC = ({ variant, ...props }) => { - return variant === "chip" ? ( - - ) : ( - - ); + return variant === "chip" ? ( + + ) : ( + + ); }; diff --git a/src/config/constants.ts b/src/config/constants.ts index c552e6a..1264bb6 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,2 +1,4 @@ -export const OPTION_TRADING_API_REST_URL = process.env.RSBUILD_REST_URL || "https://options-trading-api.deriv.ai"; -export const OPTION_TRADING_API_WS_URL = process.env.RSBUILD_WS_URL || "wss://options-trading-api.deriv.ai"; +export const OPTION_TRADING_API_REST_URL = + process.env.RSBUILD_REST_URL || "https://mock.mobile-bot.deriv.dev"; +export const OPTION_TRADING_API_WS_URL = + process.env.RSBUILD_WS_URL || "wss://mock.mobile-bot.deriv.dev"; diff --git a/src/config/tradeTypes.ts b/src/config/tradeTypes.ts index fc1499b..2068461 100644 --- a/src/config/tradeTypes.ts +++ b/src/config/tradeTypes.ts @@ -1,165 +1,172 @@ import { TradeAction } from "@/hooks/useTradeActions"; export interface TradeButton { - title: string; - label: string; - className: string; - position: 'left' | 'right'; - actionName: TradeAction; - contractType: string; // The actual contract type to use with the API (e.g., "CALL", "PUT") + title: string; + label: string; + className: string; + position: "left" | "right"; + actionName: TradeAction; + contractType: string; // The actual contract type to use with the API (e.g., "CALL", "PUT") } export interface TradeTypeConfig { - fields: { - duration: boolean; - stake: boolean; - allowEquals?: boolean; - }; - buttons: TradeButton[]; - payouts: { - max: boolean; // Whether to show max payout - labels: Record; // Map button actionName to payout label - }; - metadata?: { - preloadFields?: boolean; // If true, preload field components when trade type is selected - preloadActions?: boolean; // If true, preload action handlers - }; + fields: { + duration: boolean; + stake: boolean; + allowEquals?: boolean; + }; + buttons: TradeButton[]; + payouts: { + max: boolean; // Whether to show max payout + labels: Record; // Map button actionName to payout label + }; + metadata?: { + preloadFields?: boolean; // If true, preload field components when trade type is selected + preloadActions?: boolean; // If true, preload action handlers + }; } export const tradeTypeConfigs: Record = { - rise_fall: { - fields: { - duration: true, - stake: true, - allowEquals: false - }, - metadata: { - preloadFields: true, // Most common trade type, preload fields - preloadActions: true - }, - payouts: { - max: true, - labels: { - buy_rise: "Payout (Rise)", - buy_fall: "Payout (Fall)" - } - }, - buttons: [ - { - title: "Rise", - label: "Payout", - className: "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", - position: "right", - actionName: "buy_rise", - contractType: "CALL" - }, - { - title: "Fall", - label: "Payout", - className: "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", - position: "left", - actionName: "buy_fall", - contractType: "PUT" - } - ] - }, - high_low: { - fields: { - duration: true, - stake: true - }, - metadata: { - preloadFields: false, - preloadActions: false + rise_fall: { + fields: { + duration: true, + stake: true, + allowEquals: false, + }, + metadata: { + preloadFields: true, // Most common trade type, preload fields + preloadActions: true, + }, + payouts: { + max: true, + labels: { + buy_rise: "Payout (Rise)", + buy_fall: "Payout (Fall)", + }, + }, + buttons: [ + { + title: "Rise", + label: "Payout", + className: + "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", + position: "right", + actionName: "buy_rise", + contractType: "CALL", + }, + { + title: "Fall", + label: "Payout", + className: + "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", + position: "left", + actionName: "buy_fall", + contractType: "PUT", + }, + ], }, - payouts: { - max: true, - labels: { - buy_higher: "Payout (Higher)", - buy_lower: "Payout (Lower)" - } - }, - buttons: [ - { - title: "Higher", - label: "Payout", - className: "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", - position: "right", - actionName: "buy_higher", - contractType: "CALL" - }, - { - title: "Lower", - label: "Payout", - className: "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", - position: "left", - actionName: "buy_lower", - contractType: "PUT" - } - ] - }, - touch: { - fields: { - duration: true, - stake: true + high_low: { + fields: { + duration: true, + stake: true, + }, + metadata: { + preloadFields: false, + preloadActions: false, + }, + payouts: { + max: true, + labels: { + buy_higher: "Payout (Higher)", + buy_lower: "Payout (Lower)", + }, + }, + buttons: [ + { + title: "Higher", + label: "Payout", + className: + "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", + position: "right", + actionName: "buy_higher", + contractType: "CALL", + }, + { + title: "Lower", + label: "Payout", + className: + "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", + position: "left", + actionName: "buy_lower", + contractType: "PUT", + }, + ], }, - metadata: { - preloadFields: false, - preloadActions: false - }, - payouts: { - max: true, - labels: { - buy_touch: "Payout (Touch)", - buy_no_touch: "Payout (No Touch)" - } + touch: { + fields: { + duration: true, + stake: true, + }, + metadata: { + preloadFields: false, + preloadActions: false, + }, + payouts: { + max: true, + labels: { + buy_touch: "Payout (Touch)", + buy_no_touch: "Payout (No Touch)", + }, + }, + buttons: [ + { + title: "Touch", + label: "Payout", + className: + "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", + position: "right", + actionName: "buy_touch", + contractType: "TOUCH", + }, + { + title: "No Touch", + label: "Payout", + className: + "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", + position: "left", + actionName: "buy_no_touch", + contractType: "NOTOUCH", + }, + ], }, - buttons: [ - { - title: "Touch", - label: "Payout", - className: "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", - position: "right", - actionName: "buy_touch", - contractType: "TOUCH" - }, - { - title: "No Touch", - label: "Payout", - className: "bg-color-solid-cherry-700 hover:bg-color-solid-cherry-600", - position: "left", - actionName: "buy_no_touch", - contractType: "NOTOUCH" - } - ] - }, - multiplier: { - fields: { - duration: true, - stake: true, - allowEquals: false - }, - metadata: { - preloadFields: false, - preloadActions: false - }, - payouts: { - max: true, - labels: { - buy_multiplier: "Potential Profit" - } + multiplier: { + fields: { + duration: true, + stake: true, + allowEquals: false, + }, + metadata: { + preloadFields: false, + preloadActions: false, + }, + payouts: { + max: true, + labels: { + buy_multiplier: "Potential Profit", + }, + }, + buttons: [ + { + title: "Buy", + label: "Payout", + className: + "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", + position: "right", + actionName: "buy_multiplier", + contractType: "MULTUP", + }, + ], }, - buttons: [ - { - title: "Buy", - label: "Payout", - className: "bg-color-solid-emerald-700 hover:bg-color-solid-emerald-600", - position: "right", - actionName: "buy_multiplier", - contractType: "MULTUP" - } - ] - } }; export type TradeType = keyof typeof tradeTypeConfigs; diff --git a/src/layouts/MainLayout/MainLayout.tsx b/src/layouts/MainLayout/MainLayout.tsx index da16ee0..119da3d 100644 --- a/src/layouts/MainLayout/MainLayout.tsx +++ b/src/layouts/MainLayout/MainLayout.tsx @@ -82,7 +82,7 @@ export const MainLayout: React.FC = ({ children }) => {
) : ( -
+
{children} {isBottomNavVisible && (
diff --git a/src/layouts/MainLayout/ResponsiveHeader.tsx b/src/layouts/MainLayout/ResponsiveHeader.tsx index 8dadce4..1477012 100644 --- a/src/layouts/MainLayout/ResponsiveHeader.tsx +++ b/src/layouts/MainLayout/ResponsiveHeader.tsx @@ -27,7 +27,7 @@ export const ResponsiveHeader: React.FC = ({ {showLogo && ( Champion Trader Logo diff --git a/src/screens/TradePage/components/TradeTypesListController.tsx b/src/screens/TradePage/components/TradeTypesListController.tsx index cc3b83d..bf64ee6 100644 --- a/src/screens/TradePage/components/TradeTypesListController.tsx +++ b/src/screens/TradePage/components/TradeTypesListController.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import { getProducts } from "@/services/api/rest/product/service"; -import { TradeTypesList } from "@/components/TradeTypes"; +import { TabList, Tab } from "@/components/ui/tab-list"; import { useTradeStore } from "@/stores/tradeStore"; import { TradeType } from "@/config/tradeTypes"; @@ -23,17 +23,16 @@ export const TradeTypesListController: React.FC = () => { setIsLoading(true); const response = await getProducts(); setTradeTypes(response.data.products); + // setTradeTypes([ + // { id: "rise_fall", display_name: "Rise/Fall" }, + // { id: "high_low", display_name: "Higher/Lower" }, + // { id: "touch", display_name: "Touch/No Touch" }, + // { id: "multiplier", display_name: "Multiplier" }, + // ]); setError(null); } catch (err) { setError("Failed to load trade types"); console.error(err); - // Fallback to default trade types if API fails - setTradeTypes([ - { id: "rise_fall", display_name: "Rise/Fall" }, - { id: "high_low", display_name: "Higher/Lower" }, - { id: "touch", display_name: "Touch/No Touch" }, - { id: "multiplier", display_name: "Multiplier" }, - ]); } finally { setIsLoading(false); } @@ -56,12 +55,19 @@ export const TradeTypesListController: React.FC = () => { return
Error: {error}
; } - // Render TradeTypesList component + // Transform products into the format expected by TabList + const tabs: Tab[] = tradeTypes.map((product) => ({ + label: product.display_name, + value: product.id, + })); + + // Render TabList component directly return ( - ); }; From af061fe11c15dcb8eba902e5847873005447760d Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Wed, 5 Mar 2025 12:45:44 +0400 Subject: [PATCH 07/17] styles: some UI changes for Header and MainLayout --- src/components/SideNav/SideNav.tsx | 70 +++++++++---------- src/components/Sidebar/Sidebar.tsx | 48 +++++++------ src/config/constants.ts | 4 +- .../components/TradeTypesListController.tsx | 14 ++-- src/services/api/rest/balance/service.ts | 37 +++++----- 5 files changed, 93 insertions(+), 80 deletions(-) diff --git a/src/components/SideNav/SideNav.tsx b/src/components/SideNav/SideNav.tsx index bc4dbc7..c666be5 100644 --- a/src/components/SideNav/SideNav.tsx +++ b/src/components/SideNav/SideNav.tsx @@ -17,18 +17,18 @@ export const SideNav: React.FC = () => {
); diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 1c70a9a..a444983 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -1,28 +1,36 @@ import React from "react"; interface SidebarProps { - isOpen: boolean; - onClose: () => void; - title: string; - children: React.ReactNode; + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; } -export const Sidebar: React.FC = ({ isOpen, onClose, title, children }) => { - return ( -
-
-

{title}

- -
-
- {children} -
-
- ); +export const Sidebar: React.FC = ({ + isOpen, + onClose, + title, + children, +}) => { + return ( +
+
+

{title}

+ +
+
{children}
+
+ ); }; export default Sidebar; diff --git a/src/config/constants.ts b/src/config/constants.ts index 1264bb6..5c2d3de 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,4 +1,4 @@ export const OPTION_TRADING_API_REST_URL = - process.env.RSBUILD_REST_URL || "https://mock.mobile-bot.deriv.dev"; + process.env.RSBUILD_REST_URL || "https://options-trading-api.deriv.ai"; export const OPTION_TRADING_API_WS_URL = - process.env.RSBUILD_WS_URL || "wss://mock.mobile-bot.deriv.dev"; + process.env.RSBUILD_WS_URL || "wss://options-trading-api.deriv.ai"; diff --git a/src/screens/TradePage/components/TradeTypesListController.tsx b/src/screens/TradePage/components/TradeTypesListController.tsx index bf64ee6..9e7235d 100644 --- a/src/screens/TradePage/components/TradeTypesListController.tsx +++ b/src/screens/TradePage/components/TradeTypesListController.tsx @@ -63,11 +63,13 @@ export const TradeTypesListController: React.FC = () => { // Render TabList component directly return ( - +
+ +
); }; diff --git a/src/services/api/rest/balance/service.ts b/src/services/api/rest/balance/service.ts index 03ea9ba..6df09f0 100644 --- a/src/services/api/rest/balance/service.ts +++ b/src/services/api/rest/balance/service.ts @@ -1,21 +1,24 @@ -import axios from 'axios'; -import { apiConfig } from '@/config/api'; -import { useClientStore } from '@/stores/clientStore'; +import axios from "axios"; +import { apiConfig } from "@/config/api"; +import { useClientStore } from "@/stores/clientStore"; export const fetchBalance = async () => { - if (typeof useClientStore.getState !== 'function') return; - const token = useClientStore.getState().token; - if (!token) return; + if (typeof useClientStore.getState !== "function") return; + const token = useClientStore.getState().token; + if (!token) return; - try { - const response = await axios.get(`${apiConfig.rest.baseUrl}/balance`, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); - const { balance, currency } = response.data; - useClientStore.getState().setBalance(balance, currency); - } catch (error) { - console.error('Failed to fetch balance:', error); - } + try { + const response = await axios.get( + `${apiConfig.rest.baseUrl}/v1/accounting/balance`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + const { balance, currency } = response.data; + useClientStore.getState().setBalance(balance, currency); + } catch (error) { + console.error("Failed to fetch balance:", error); + } }; From 105e96abacb448cb724266b58ed51b21a848d4ac Mon Sep 17 00:00:00 2001 From: Aum Bhatt Date: Wed, 5 Mar 2025 15:24:02 +0400 Subject: [PATCH 08/17] feat: more UI changes for the AccountSwitcher --- .../AccountSwitcher/AccountSwitcher.tsx | 126 ++- .../Duration/DurationController.tsx | 222 ++--- src/components/HowToTrade/HowToTrade.tsx | 70 +- src/components/ui/tab-list.tsx | 6 +- src/layouts/MainLayout/MainLayout.tsx | 6 +- src/layouts/MainLayout/ResponsiveHeader.tsx | 7 +- src/screens/TradePage/TradePage.tsx | 2 +- .../components/TradeFormController.tsx | 845 ++++++++++-------- .../components/TradeTypesListController.tsx | 10 +- 9 files changed, 694 insertions(+), 600 deletions(-) diff --git a/src/components/AccountSwitcher/AccountSwitcher.tsx b/src/components/AccountSwitcher/AccountSwitcher.tsx index be67610..d83c811 100644 --- a/src/components/AccountSwitcher/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher/AccountSwitcher.tsx @@ -1,69 +1,67 @@ -import React, { useState } from "react" -import { ChevronDown, ChevronUp } from "lucide-react" -import { useClientStore } from "@/stores/clientStore" -import { useAccount } from "@/hooks/useAccount" +import React, { useState } from "react"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import { useClientStore } from "@/stores/clientStore"; +import { useAccount } from "@/hooks/useAccount"; import { - AccountPopover, - AccountPopoverContent, - AccountPopoverTrigger, -} from "@/components/ui/account-popover" -import { AccountInfo } from "./AccountInfo" -import { useOrientationStore } from "@/stores/orientationStore" + AccountPopover, + AccountPopoverContent, + AccountPopoverTrigger, +} from "@/components/ui/account-popover"; +import { AccountInfo } from "./AccountInfo"; +import { useOrientationStore } from "@/stores/orientationStore"; export const AccountSwitcher: React.FC = () => { - const { balance } = useClientStore() - const { selectedAccount } = useAccount() - const { isLandscape } = useOrientationStore() - const [isOpen, setIsOpen] = useState(false) + const { balance } = useClientStore(); + const { selectedAccount } = useAccount(); + const { isLandscape } = useOrientationStore(); + const [isOpen, setIsOpen] = useState(false); - return ( - - - - - - setIsOpen(false)} /> - - - ) -} + return ( + + + + + + setIsOpen(false)} /> + + + ); +}; -export default AccountSwitcher +export default AccountSwitcher; diff --git a/src/components/Duration/DurationController.tsx b/src/components/Duration/DurationController.tsx index a06840f..f7e1bc0 100644 --- a/src/components/Duration/DurationController.tsx +++ b/src/components/Duration/DurationController.tsx @@ -13,130 +13,138 @@ import type { DurationRangesResponse } from "@/services/api/rest/duration/types" import { useOrientationStore } from "@/stores/orientationStore"; const DURATION_TYPES: Tab[] = [ - { label: "Ticks", value: "tick" }, - { label: "Seconds", value: "second" }, - { label: "Minutes", value: "minute" }, - { label: "Hours", value: "hour" }, - // { label: "End Time", value: "day" }, + { label: "Ticks", value: "tick" }, + { label: "Seconds", value: "second" }, + { label: "Minutes", value: "minute" }, + { label: "Hours", value: "hour" }, + // { label: "End Time", value: "day" }, ] as const; type DurationType = keyof DurationRangesResponse; interface DurationControllerProps { - onClose?: () => void; + onClose?: () => void; } export const DurationController: React.FC = ({ - onClose, + onClose, }) => { - const { duration, setDuration } = useTradeStore(); - const { isLandscape } = useOrientationStore(); - const { setBottomSheet } = useBottomSheetStore(); - const isInitialRender = useRef(true); + const { duration, setDuration } = useTradeStore(); + const { isLandscape } = useOrientationStore(); + const { setBottomSheet } = useBottomSheetStore(); + const isInitialRender = useRef(true); - useEffect(() => { - isInitialRender.current = true; - return () => { - isInitialRender.current = false; - }; - }, []); - - // Initialize local state for both mobile and desktop - const [localDuration, setLocalDuration] = React.useState(duration); - const [value, type] = localDuration.split(" "); - const selectedType = type as DurationType; - const selectedValue: string | number = - type === "hour" ? value : parseInt(value, 10); + useEffect(() => { + isInitialRender.current = true; + return () => { + isInitialRender.current = false; + }; + }, []); - // Use debounced updates for desktop scroll - useDebounce( - localDuration, - (value) => { - if (isLandscape) { - setDuration(value); - } - }, - 300 - ); + // Initialize local state for both mobile and desktop + const [localDuration, setLocalDuration] = React.useState(duration); + const [value, type] = localDuration.split(" "); + const selectedType = type as DurationType; + const selectedValue: string | number = + type === "hour" ? value : parseInt(value, 10); - const handleTypeSelect = (type: DurationType) => { - const newDuration = - type === "hour" ? "1:0 hour" : `${getDurationValues(type)[0]} ${type}`; - setLocalDuration(newDuration); - }; + // Use debounced updates for desktop scroll + useDebounce( + localDuration, + (value) => { + if (isLandscape) { + setDuration(value); + } + }, + 300 + ); - const handleValueSelect = (value: number | string) => { - const newDuration = `${value} ${selectedType}`; - setLocalDuration(newDuration); - }; + const handleTypeSelect = (type: DurationType) => { + const newDuration = + type === "hour" + ? "1:0 hour" + : `${getDurationValues(type)[0]} ${type}`; + setLocalDuration(newDuration); + }; - const handleValueClick = (value: number | string) => { - const newDuration = `${value} ${selectedType}`; - setLocalDuration(newDuration); - setDuration(newDuration); // Update store immediately on click - if (isLandscape) { - onClose?.(); - } - }; + const handleValueSelect = (value: number | string) => { + const newDuration = `${value} ${selectedType}`; + setLocalDuration(newDuration); + }; - const handleSave = () => { - setDuration(localDuration); - if (isLandscape) { - onClose?.(); - } else { - setBottomSheet(false); - } - }; + const handleValueClick = (value: number | string) => { + const newDuration = `${value} ${selectedType}`; + setLocalDuration(newDuration); + setDuration(newDuration); // Update store immediately on click + if (isLandscape) { + onClose?.(); + } + }; - const content = ( - <> -
- {!isLandscape && } - void} - variant={isLandscape ? "vertical" : "chip"} - /> -
- {selectedType === "hour" ? ( - { - handleValueSelect(value); - }} - onValueClick={handleValueClick} - isInitialRender={isInitialRender} - /> - ) : ( - - )} -
-
- {!isLandscape && ( -
- - Save - -
- )} - - ); + const handleSave = () => { + setDuration(localDuration); + if (isLandscape) { + onClose?.(); + } else { + setBottomSheet(false); + } + }; - if (isLandscape) { - return ( - -
{content}
-
+ const content = ( + <> +
+ {!isLandscape && } +
+ void} + variant={isLandscape ? "vertical" : "chip"} + /> +
+
+ {selectedType === "hour" ? ( + { + handleValueSelect(value); + }} + onValueClick={handleValueClick} + isInitialRender={isInitialRender} + /> + ) : ( + + )} +
+
+ {!isLandscape && ( +
+ + Save + +
+ )} + ); - } - return
{content}
; + if (isLandscape) { + return ( + +
{content}
+
+ ); + } + + return
{content}
; }; diff --git a/src/components/HowToTrade/HowToTrade.tsx b/src/components/HowToTrade/HowToTrade.tsx index 5532bb5..faa90fb 100644 --- a/src/components/HowToTrade/HowToTrade.tsx +++ b/src/components/HowToTrade/HowToTrade.tsx @@ -4,42 +4,46 @@ import { useDeviceDetection } from "@/hooks/useDeviceDetection"; import { GuideModal } from "./GuideModal"; import { ChevronRight } from "lucide-react"; -export const HowToTrade: React.FC = () => { - const { setBottomSheet } = useBottomSheetStore(); - const { isDesktop } = useDeviceDetection(); - const [isModalOpen, setIsModalOpen] = useState(false); +type HowToTradeProps = { + productName: string; +}; + +export const HowToTrade: React.FC = ({ productName }) => { + const { setBottomSheet } = useBottomSheetStore(); + const { isDesktop } = useDeviceDetection(); + const [isModalOpen, setIsModalOpen] = useState(false); - const handleClick = () => { - if (isDesktop) { - setIsModalOpen(true); - } else { - setBottomSheet(true, "how-to-trade", "80%", { - show: true, - label: "Got it", - onClick: () => setBottomSheet(false) - }); - } - }; + const handleClick = () => { + if (isDesktop) { + setIsModalOpen(true); + } else { + setBottomSheet(true, "how-to-trade", "80%", { + show: true, + label: "Got it", + onClick: () => setBottomSheet(false), + }); + } + }; - return ( - <> - + return ( + <> + - {isDesktop && ( - setIsModalOpen(false)} - type="rise-fall" - /> - )} - - ); + {isDesktop && ( + setIsModalOpen(false)} + type="rise-fall" + /> + )} + + ); }; export default HowToTrade; diff --git a/src/components/ui/tab-list.tsx b/src/components/ui/tab-list.tsx index a37fa93..09bf241 100644 --- a/src/components/ui/tab-list.tsx +++ b/src/components/ui/tab-list.tsx @@ -28,7 +28,7 @@ const ChipTabList: React.FC = ({ msOverflowStyle: "none", }} > -
+
{tabs.map(({ label, value }) => (
+ ) : ( + + Log in + + )} +
+ ) : ( + <> + )} +
+
- {config.fields.allowEquals && } -
- -
- {config.buttons.map((button) => ( - Loading...
} - > - div]:px-2 [&_span]:text-sm`} - title={button.title} - label={button.label} - value={ - buttonStates[button.actionName]?.loading - ? "Loading..." - : `${ - // added for demo proposes will change it to 0 once api is connected - buttonStates[button.actionName]?.payout || 10 - } ${currency}` - } - title_position={button.position} - disabled={ - buttonStates[button.actionName]?.loading - // Commenting it as api is not working we'll enable it once api is working - // buttonStates[button.actionName]?.error !== null - } - loading={ - buttonStates[button.actionName]?.loading - // Commenting it as api is not working we'll enable it once api is working - // buttonStates[button.actionName]?.reconnecting - } - error={buttonStates[button.actionName]?.error} - onClick={() => { - if (!isLoggedIn) return; - // Comment out actual API call but keep the success flow - // await tradeActions[button.actionName]() - - // Open positions sidebar only in desktop view - if (isLandscape) { - setSidebar('positions') - } - - // Show trade notification - toast({ - content: ( - - ), - variant: "black", - duration: 3000, - position: isLandscape ? 'bottom-left' : 'top-center' - }) - }} - /> - - ))} -
-
- -
-
- ) : ( - // Mobile layout - <> -
-
- - {config.fields.duration && ( - Loading duration field...
}> - { - const durationField = document.querySelector( - 'button[aria-label^="Duration"]' - ) - if (durationField) { - ;(durationField as HTMLButtonElement).click() - } - }} + {isLandscape ? ( + // Desktop layout +
+
{ + // When clicking anywhere in the trade fields section, hide any open controllers + const event = new MouseEvent("mousedown", { + bubbles: true, + cancelable: true, + }); + document.dispatchEvent(event); + }} > - - - - )} - {config.fields.stake && ( - Loading stake field...
}> - { - const stakeField = document.querySelector( - 'button[aria-label^="Stake"]' - ) - if (stakeField) { - ;(stakeField as HTMLButtonElement).click() - } - }} +
+ {config.fields.duration && ( + Loading duration field...
+ } + > + + + )} + {config.fields.stake && ( + Loading stake field...
} + > +
+ + + setIsStakeSelected(selected) + } + onError={(error) => + setStakeError(error) + } + /> + +
+ + )} +
+ {config.fields.allowEquals && } + + +
- - - - )} - - {config.fields.allowEquals && ( - Loading equals controller...
}> -
- -
- - )} - - + {config.buttons.map((button) => ( + Loading...} + > + div]:px-2 [&_span]:text-sm`} + title={button.title} + label={button.label} + value={ + buttonStates[button.actionName]?.loading + ? "Loading..." + : `${ + // added for demo proposes will change it to 0 once api is connected + buttonStates[ + button.actionName + ]?.payout || 10 + } ${currency}` + } + title_position={button.position} + disabled={ + buttonStates[button.actionName]?.loading + // Commenting it as api is not working we'll enable it once api is working + // buttonStates[button.actionName]?.error !== null + } + loading={ + buttonStates[button.actionName]?.loading + // Commenting it as api is not working we'll enable it once api is working + // buttonStates[button.actionName]?.reconnecting + } + error={ + buttonStates[button.actionName]?.error + } + onClick={() => { + if (!isLoggedIn) return; + // Comment out actual API call but keep the success flow + // await tradeActions[button.actionName]() -
- {config.buttons.map((button) => ( - Loading...
} - > - { - if (!isLoggedIn) return; - // Comment out actual API call but keep the success flow - // await tradeActions[button.actionName]() - - // Open positions sidebar only in desktop view - if (isLandscape) { - setSidebar('positions') - } + // Open positions sidebar only in desktop view + if (isLandscape) { + setSidebar("positions"); + } - // Show trade notification - toast({ - content: ( - - ), - variant: "black", - duration: 3000, - position: isLandscape ? 'bottom-left' : 'top-center' - }) - }} - /> -
- ))} - - - )} - - ) -} + // Show trade notification + toast({ + content: ( + + ), + variant: "black", + duration: 3000, + position: isLandscape + ? "bottom-left" + : "top-center", + }); + }} + /> + + ))} + +
+ +
+ + ) : ( + // Mobile layout + <> +
+
+ + {config.fields.duration && ( + Loading duration field...
+ } + > + { + const durationField = + document.querySelector( + 'button[aria-label^="Duration"]' + ); + if (durationField) { + ( + durationField as HTMLButtonElement + ).click(); + } + }} + > + + + + )} + {config.fields.stake && ( + Loading stake field...
+ } + > + { + const stakeField = + document.querySelector( + 'button[aria-label^="Stake"]' + ); + if (stakeField) { + ( + stakeField as HTMLButtonElement + ).click(); + } + }} + > + + + + )} + + {config.fields.allowEquals && ( + Loading equals controller... + } + > +
+ +
+
+ )} + + + +
+ {config.buttons.map((button) => ( + Loading...
} + > + { + if (!isLoggedIn) return; + // Comment out actual API call but keep the success flow + // await tradeActions[button.actionName]() + + // Open positions sidebar only in desktop view + if (isLandscape) { + setSidebar("positions"); + } + + // Show trade notification + toast({ + content: ( + + ), + variant: "black", + duration: 3000, + position: isLandscape + ? "bottom-left" + : "top-center", + }); + }} + /> + + ))} + + + )} + + ); +}; diff --git a/src/screens/TradePage/components/TradeTypesListController.tsx b/src/screens/TradePage/components/TradeTypesListController.tsx index 9e7235d..461bb56 100644 --- a/src/screens/TradePage/components/TradeTypesListController.tsx +++ b/src/screens/TradePage/components/TradeTypesListController.tsx @@ -48,11 +48,15 @@ export const TradeTypesListController: React.FC = () => { // Show loading state or error if (isLoading) { - return
Loading trade types...
; + return ( +
+ Loading trade types... +
+ ); } if (error && tradeTypes.length === 0) { - return
Error: {error}
; + return
Error: {error}
; } // Transform products into the format expected by TabList @@ -63,7 +67,7 @@ export const TradeTypesListController: React.FC = () => { // Render TabList component directly return ( -
+
Date: Wed, 5 Mar 2025 15:52:48 +0400 Subject: [PATCH 09/17] fix: responsive UI and unit tests --- .../HowToTrade/__tests__/HowToTrade.test.tsx | 62 ++++++++++--------- src/components/MarketInfo/MarketInfo.tsx | 2 +- src/layouts/MainLayout/ResponsiveHeader.tsx | 2 +- src/screens/TradePage/TradePage.tsx | 2 +- .../components/TradeTypesListController.tsx | 10 ++- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/components/HowToTrade/__tests__/HowToTrade.test.tsx b/src/components/HowToTrade/__tests__/HowToTrade.test.tsx index c52bf75..d23da1f 100644 --- a/src/components/HowToTrade/__tests__/HowToTrade.test.tsx +++ b/src/components/HowToTrade/__tests__/HowToTrade.test.tsx @@ -1,39 +1,41 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import HowToTrade from '../HowToTrade'; +import { render, screen, fireEvent } from "@testing-library/react"; +import HowToTrade from "../HowToTrade"; const mockSetBottomSheet = jest.fn(); // Mock the bottomSheetStore -jest.mock('@/stores/bottomSheetStore', () => ({ - useBottomSheetStore: () => ({ - setBottomSheet: mockSetBottomSheet - }) +jest.mock("@/stores/bottomSheetStore", () => ({ + useBottomSheetStore: () => ({ + setBottomSheet: mockSetBottomSheet, + }), })); -describe('HowToTrade', () => { - beforeEach(() => { - mockSetBottomSheet.mockClear(); - }); +describe("HowToTrade", () => { + beforeEach(() => { + mockSetBottomSheet.mockClear(); + }); - it('renders correctly', () => { - render(); - expect(screen.getByText('How to trade Rise/Fall?')).toBeInTheDocument(); - }); + it("renders correctly", () => { + render(); + expect(screen.getByText("How to trade Rise/Fall?")).toBeInTheDocument(); + }); - it('opens bottom sheet when clicked', () => { - render(); - fireEvent.click(screen.getByText('How to trade Rise/Fall?')); - - const call = mockSetBottomSheet.mock.calls[0]; - expect(call[0]).toBe(true); - expect(call[1]).toBe('how-to-trade'); - expect(call[2]).toBe('80%'); - expect(call[3]).toEqual(expect.objectContaining({ - show: true, - label: "Got it", - onClick: expect.any(Function) - })); - call[3].onClick(); - expect(mockSetBottomSheet).toHaveBeenCalledWith(false); - }); + it("opens bottom sheet when clicked", () => { + render(); + fireEvent.click(screen.getByText("How to trade Rise/Fall?")); + + const call = mockSetBottomSheet.mock.calls[0]; + expect(call[0]).toBe(true); + expect(call[1]).toBe("how-to-trade"); + expect(call[2]).toBe("80%"); + expect(call[3]).toEqual( + expect.objectContaining({ + show: true, + label: "Got it", + onClick: expect.any(Function), + }) + ); + call[3].onClick(); + expect(mockSetBottomSheet).toHaveBeenCalledWith(false); + }); }); diff --git a/src/components/MarketInfo/MarketInfo.tsx b/src/components/MarketInfo/MarketInfo.tsx index 194ca4d..fdced9f 100644 --- a/src/components/MarketInfo/MarketInfo.tsx +++ b/src/components/MarketInfo/MarketInfo.tsx @@ -21,7 +21,7 @@ export const MarketInfo: React.FC = ({ if (isMobile) { return (
= ({ return (