diff --git a/src/App.tsx b/src/App.tsx index 269b97b328..e93f8ddf80 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import ErrorBoundary from 'Components/common/error-boundary'; -import Layout from 'Components/layout'; +import Layout from 'Components/layout/Layout'; const App = () => ( diff --git a/src/api/hooks/useAPI.ts b/src/api/hooks/useAPI.ts index 385e5aa55e..b58f9a73d8 100644 --- a/src/api/hooks/useAPI.ts +++ b/src/api/hooks/useAPI.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import type { TSocketEndpointNames, + TSocketError, TSocketPaginateableEndpointNames, TSocketRequestPayload, TSocketResponseData, @@ -36,7 +37,7 @@ const useAPI = () => { ): { subscribe: ( onData: (response: Promise>) => void, - onError: (response: Promise>) => void + onError: (response: Promise>) => void ) => { unsubscribe?: VoidFunction }; } => socketConnection?.subscribe({ [name]: 1, subscribe: 1, ...(payload || {}) }), [socketConnection] diff --git a/src/api/hooks/useActiveSymbols.ts b/src/api/hooks/useActiveSymbols.ts new file mode 100644 index 0000000000..9cde6c4c71 --- /dev/null +++ b/src/api/hooks/useActiveSymbols.ts @@ -0,0 +1,23 @@ +import { useMemo } from 'react'; +import useQuery from './useQuery'; +import useLogin from './useLogin'; + +const useActiveSymbols = () => { + const { isLoggedIn, isAuthorized } = useLogin(); + const { data: activeSymbols, ...rest } = useQuery('active_symbols', { + payload: { active_symbols: 'brief' }, + options: { enabled: isLoggedIn ? isAuthorized : true }, + }); + + const modifiedActiveSymbols = useMemo( + () => ({ ...activeSymbols?.active_symbols }), + [activeSymbols?.active_symbols] + ); + + return { + data: modifiedActiveSymbols, + ...rest, + }; +}; + +export default useActiveSymbols; diff --git a/src/api/hooks/useLogin.ts b/src/api/hooks/useLogin.ts index 10e4d17ad9..1f4c441616 100644 --- a/src/api/hooks/useLogin.ts +++ b/src/api/hooks/useLogin.ts @@ -6,11 +6,11 @@ import { useEffect, useMemo } from 'react'; const useLogin = () => { const search = window.location.search; const client_account_params = readLoginQueryParams(); - const { data: client_info } = useAuthorize(); - let is_logged_in = false; + const { data: client_info, isSuccess } = useAuthorize(); + let isLoggedIn = false; if (localStorage.getItem('active_loginId')) { - is_logged_in = true; + isLoggedIn = true; } const client_object = useMemo(() => { @@ -25,7 +25,7 @@ const useLogin = () => { if (search) deleteQueryParams(); } }, [client_info, client_account_params, client_object, search]); - return { is_logged_in }; + return { isLoggedIn, isAuthorized: isSuccess }; }; export default useLogin; diff --git a/src/api/hooks/useSubscription.ts b/src/api/hooks/useSubscription.ts new file mode 100644 index 0000000000..3c75786558 --- /dev/null +++ b/src/api/hooks/useSubscription.ts @@ -0,0 +1,66 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import useAPI from './useAPI'; +import type { + TSocketAcceptableProps, + TSocketError, + TSocketRequestPayload, + TSocketResponseData, + TSocketSubscribableEndpointNames, +} from '../types'; + +const useSubscription = (name: T) => { + const [isLoading, setIsLoading] = useState(false); + const [isSubscribed, setIsSubscribed] = useState(false); + const [error, setError] = useState>(); + const [data, setData] = useState>(); + const subscriber = useRef<{ unsubscribe?: VoidFunction }>(); + const { subscribe: _subscribe } = useAPI(); + + const subscribe = useCallback( + (...props: TSocketAcceptableProps) => { + const prop = props?.[0]; + const payload = prop && 'payload' in prop ? (prop.payload as TSocketRequestPayload) : undefined; + + setIsLoading(true); + setIsSubscribed(true); + + try { + subscriber.current = _subscribe(name, payload).subscribe( + async response => { + setData(await response); + setIsLoading(false); + }, + async response => { + setError((await response).error as unknown as TSocketError); + setIsLoading(false); + } + ); + } catch (e) { + setError(e as TSocketError); + } + }, + [_subscribe, name] + ); + + const unsubscribe = useCallback(() => { + subscriber.current?.unsubscribe?.(); + setIsSubscribed(false); + }, []); + + useEffect(() => { + return () => { + unsubscribe(); + }; + }, [unsubscribe]); + + return { + subscribe, + unsubscribe, + isLoading, + isSubscribed, + error, + data, + }; +}; + +export default useSubscription; diff --git a/src/api/hooks/useTicksHistory.ts b/src/api/hooks/useTicksHistory.ts new file mode 100644 index 0000000000..82aa28bed7 --- /dev/null +++ b/src/api/hooks/useTicksHistory.ts @@ -0,0 +1,37 @@ +import { useCallback } from 'react'; +import useSubscription from './useSubscription'; + +import { TSocketRequestPayload } from 'Api/types'; + +type TTicksHistoryPayload = TSocketRequestPayload<'ticks_history'>['payload']; + +const useTicksHistory = () => { + const { subscribe: _subscribe, unsubscribe, data, ...rest } = useSubscription('ticks_history'); + + const subscribe = useCallback( + (symbol: TTicksHistoryPayload['ticks_history'], granularity: TTicksHistoryPayload['granularity']) => { + const style: TTicksHistoryPayload['style'] = granularity ? 'candles' : 'ticks'; + + return _subscribe({ + payload: { + adjust_start_time: 1, + ticks_history: symbol, + end: 'latest', + count: 1000, + granularity: granularity || undefined, + style, + }, + }); + }, + [_subscribe] + ); + + return { + subscribe, + unsubscribe, + data, + ...rest, + }; +}; + +export default useTicksHistory; diff --git a/src/api/types.ts b/src/api/types.ts index 063d70aeb0..85a4ba408e 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -589,7 +589,7 @@ type TSocketEndpoints = { response: StatesListResponse; }; ticks_history: { - request: TicksHistoryRequest; + request: Omit & { count?: number | string }; // count type from @deriv-api is wrong. Hence redefinition. response: TicksHistoryResponse; }; ticks: { @@ -731,3 +731,27 @@ export type TSocketPaginateableEndpointNames = KeysMatching< TSocketEndpoints, { request: { limit?: number; offset?: number } } >; + +export type TSocketError = { + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Error object. + */ + error: { + code: string; + message: string; + }; + /** + * Action name of the request made. + */ + msg_type: T; + /** + * [Optional] Used to map request to response. + */ + req_id?: number; +}; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx new file mode 100644 index 0000000000..ad6de7970f --- /dev/null +++ b/src/components/layout/Layout.tsx @@ -0,0 +1,15 @@ +import { Fragment, PropsWithChildren } from 'react'; +import Header from './header'; +import Footer from './footer'; + +const Layout = ({ children }: PropsWithChildren) => { + return ( + +
+ {children} +