From ef66dd9eeac079ee903b5f8fb737f4ecc052b19a Mon Sep 17 00:00:00 2001
From: Farzin Mirzaie <72082844+farzin-fs@users.noreply.github.com>
Date: Wed, 19 Oct 2022 13:07:54 +0330
Subject: [PATCH] Farzin/77435/add `useSubscription` hook (#6621)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(cashier): :bug: fix `tsconfig`
* feat(shared): :sparkles: expose `WS` object from `shared` package via `useWS`
* feat(cashier): :sparkles: add `useCountdown` hook
* feat(cashier): :sparkles: add `useWS` hook
* feat(cashier): :sparkles: add `useStore` hook and `StoreContext`
* feat(cashier): :sparkles: add `useVerifyEmail` hook
* test(cashier): :white_check_mark: add test for `useCountdown` hook
* test(cashier): :white_check_mark: add test for `useWS` hook
* fix(cashier): :memo: resolve PR comments
* fix(cashier): :memo: resolve PR comments
* fix(cashier): :memo: resolve PR comments
* fix(cashier): :memo: resolve PR comments
* refactor(cashier): :recycle: improve types for `useWS` hook
* feat(cashier): :sparkles: add `useSubscription` hook
* test(cashier): ✅ add test for `useSubscription` hook
* feat(cashier): ✨ add `useNeedAuthentication` hook
* feat(cashier): ✨ add `useNeedFinancialAssessment` hook
* feat(cashier): ✨ add `useRealSTPAccount` hook
* feat(cashier): ✨ add `useNeedTNC` hook
* feat(cashier): ✨ add `useDepositLocked` hook
* fix(cashier): :memo: resolve PR comments
* fix(cashier): :memo: resolve PR comments
---
...untdown.test.tsx => useCountdown.spec.tsx} | 0
.../hooks/__tests__/useDepositLocked.spec.tsx | 153 +++++++++++++++++
.../__tests__/useNeedAuthentication.spec.tsx | 82 ++++++++++
.../useNeedFinancialAssessment.spec.tsx | 154 ++++++++++++++++++
.../src/hooks/__tests__/useNeedTNC.spec.tsx | 150 +++++++++++++++++
.../__tests__/useRealSTPAccount.spec.tsx | 66 ++++++++
.../src/hooks/__tests__/useStore.spec.tsx | 42 +++++
.../hooks/__tests__/useSubscription.spec.tsx | 85 ++++++++++
.../{useWS.test.tsx => useWS.spec.tsx} | 49 +++++-
packages/cashier/src/hooks/index.ts | 6 +
.../cashier/src/hooks/useDepositLocked.ts | 17 ++
.../src/hooks/useNeedAuthentication.ts | 11 ++
.../src/hooks/useNeedFinancialAssessment.ts | 12 ++
packages/cashier/src/hooks/useNeedTNC.ts | 13 ++
.../cashier/src/hooks/useRealSTPAccount.ts | 13 ++
packages/cashier/src/hooks/useSubscription.ts | 44 +++++
packages/cashier/src/hooks/useWS.ts | 2 +-
packages/cashier/src/types/types.ts | 63 ++++++-
18 files changed, 955 insertions(+), 7 deletions(-)
rename packages/cashier/src/hooks/__tests__/{useCountdown.test.tsx => useCountdown.spec.tsx} (100%)
create mode 100644 packages/cashier/src/hooks/__tests__/useDepositLocked.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useNeedAuthentication.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useNeedFinancialAssessment.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useNeedTNC.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useRealSTPAccount.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useStore.spec.tsx
create mode 100644 packages/cashier/src/hooks/__tests__/useSubscription.spec.tsx
rename packages/cashier/src/hooks/__tests__/{useWS.test.tsx => useWS.spec.tsx} (64%)
create mode 100644 packages/cashier/src/hooks/useDepositLocked.ts
create mode 100644 packages/cashier/src/hooks/useNeedAuthentication.ts
create mode 100644 packages/cashier/src/hooks/useNeedFinancialAssessment.ts
create mode 100644 packages/cashier/src/hooks/useNeedTNC.ts
create mode 100644 packages/cashier/src/hooks/useRealSTPAccount.ts
create mode 100644 packages/cashier/src/hooks/useSubscription.ts
diff --git a/packages/cashier/src/hooks/__tests__/useCountdown.test.tsx b/packages/cashier/src/hooks/__tests__/useCountdown.spec.tsx
similarity index 100%
rename from packages/cashier/src/hooks/__tests__/useCountdown.test.tsx
rename to packages/cashier/src/hooks/__tests__/useCountdown.spec.tsx
diff --git a/packages/cashier/src/hooks/__tests__/useDepositLocked.spec.tsx b/packages/cashier/src/hooks/__tests__/useDepositLocked.spec.tsx
new file mode 100644
index 000000000000..df4c71dafb32
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useDepositLocked.spec.tsx
@@ -0,0 +1,153 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider } from '../useStore';
+import useDepositLocked from '../useDepositLocked';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseDepositLockedExample = () => {
+ const is_deposit_locked = useDepositLocked();
+
+ return (
+ <>
+
{is_deposit_locked ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useDepositLocked', () => {
+ test('should be false if none of the conditions are met', () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_deposit_lock: false,
+ is_authentication_needed: false,
+ is_tnc_needed: false,
+ is_eu: false,
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_deposit_locked = screen.getByTestId('dt_is_deposit_locked');
+ expect(is_deposit_locked).toHaveTextContent('false');
+ });
+
+ test('should be true if is_deposit_lock is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_deposit_lock: true,
+ is_authentication_needed: false,
+ is_tnc_needed: false,
+ is_eu: false,
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_deposit_locked = screen.getByTestId('dt_is_deposit_locked');
+ expect(is_deposit_locked).toHaveTextContent('true');
+ });
+
+ test('should be true if is_need_tnc is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_deposit_lock: false,
+ is_authentication_needed: false,
+ is_tnc_needed: true,
+ is_eu: true,
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ mt5_login_list: [
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_deposit_locked = screen.getByTestId('dt_is_deposit_locked');
+ expect(is_deposit_locked).toHaveTextContent('true');
+ });
+
+ test('should be true if is_need_financial_assessment is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_deposit_lock: false,
+ is_authentication_needed: false,
+ is_tnc_needed: false,
+ is_eu: false,
+ is_financial_account: true,
+ is_financial_information_incomplete: true,
+ is_trading_experience_incomplete: false,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_deposit_locked = screen.getByTestId('dt_is_deposit_locked');
+ expect(is_deposit_locked).toHaveTextContent('true');
+ });
+
+ test('should be true if is_need_authentication is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_deposit_lock: false,
+ is_authentication_needed: true,
+ is_tnc_needed: false,
+ is_eu: true,
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ mt5_login_list: [
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_deposit_locked = screen.getByTestId('dt_is_deposit_locked');
+ expect(is_deposit_locked).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useNeedAuthentication.spec.tsx b/packages/cashier/src/hooks/__tests__/useNeedAuthentication.spec.tsx
new file mode 100644
index 000000000000..72395f610b3e
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useNeedAuthentication.spec.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider } from '../useStore';
+import useNeedAuthentication from '../useNeedAuthentication';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseNeedAuthenticationExample = () => {
+ const is_need_authentication = useNeedAuthentication();
+
+ return (
+ <>
+ {is_need_authentication ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useNeedAuthentication', () => {
+ test('should be false if is_authentication_needed and is_eu both are false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_authentication_needed: false,
+ is_eu: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_authentication = screen.getByTestId('dt_is_need_authentication');
+ expect(is_need_authentication).toHaveTextContent('false');
+ });
+
+ test('should be false if is_authentication_needed is false and is_eu is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_authentication_needed: false,
+ is_eu: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_authentication = screen.getByTestId('dt_is_need_authentication');
+ expect(is_need_authentication).toHaveTextContent('false');
+ });
+
+ test('should be false if is_authentication_needed is true and is_eu is false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_authentication_needed: true,
+ is_eu: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_authentication = screen.getByTestId('dt_is_need_authentication');
+ expect(is_need_authentication).toHaveTextContent('false');
+ });
+
+ test('should be true if is_authentication_needed and is_eu both are true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_authentication_needed: true,
+ is_eu: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_authentication = screen.getByTestId('dt_is_need_authentication');
+ expect(is_need_authentication).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useNeedFinancialAssessment.spec.tsx b/packages/cashier/src/hooks/__tests__/useNeedFinancialAssessment.spec.tsx
new file mode 100644
index 000000000000..1b1ad3b46f5d
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useNeedFinancialAssessment.spec.tsx
@@ -0,0 +1,154 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider } from '../useStore';
+import useNeedFinancialAssessment from '../useNeedFinancialAssessment';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseNeedFinancialAssessmentExample = () => {
+ const is_need_financial_assessment = useNeedFinancialAssessment();
+
+ return (
+ <>
+ {is_need_financial_assessment ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useNeedFinancialAssessment', () => {
+ test('should be false if is_financial_account, is_financial_information_incomplete and is_trading_experience_incomplete all are false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('false');
+ });
+
+ test('should be false if is_financial_account and is_trading_experience_incomplete are false and is_financial_information_incomplete is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: false,
+ is_financial_information_incomplete: true,
+ is_trading_experience_incomplete: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('false');
+ });
+
+ test('should be false if is_financial_account and is_financial_information_incomplete are false and is_trading_experience_incomplete is true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: false,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('false');
+ });
+
+ test('should be false if is_financial_account is false but is_financial_information_incomplete and is_trading_experience_incomplete both are true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: false,
+ is_financial_information_incomplete: true,
+ is_trading_experience_incomplete: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('false');
+ });
+
+ test('should be false if is_financial_account is true but is_financial_information_incomplete and is_trading_experience_incomplete both are false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: true,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('false');
+ });
+
+ test('should be true if is_financial_account and is_financial_information_incomplete are true and is_trading_experience_incomplete is false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: true,
+ is_financial_information_incomplete: true,
+ is_trading_experience_incomplete: false,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('true');
+ });
+
+ test('should be true if is_financial_account and is_trading_experience_incomplete are true and is_financial_information_incomplete is false', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: true,
+ is_financial_information_incomplete: false,
+ is_trading_experience_incomplete: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('true');
+ });
+
+ test('should be true if is_financial_account, is_financial_information_incomplete and is_trading_experience_incomplete all are true', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_financial_account: true,
+ is_financial_information_incomplete: true,
+ is_trading_experience_incomplete: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_financial_assessment = screen.getByTestId('dt_is_need_financial_assessment');
+ expect(is_need_financial_assessment).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useNeedTNC.spec.tsx b/packages/cashier/src/hooks/__tests__/useNeedTNC.spec.tsx
new file mode 100644
index 000000000000..d2bf7d6f1a2c
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useNeedTNC.spec.tsx
@@ -0,0 +1,150 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider } from '../useStore';
+import useNeedTNC from '../useNeedTNC';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseNeedTNCExample = () => {
+ const is_need_tnc = useNeedTNC();
+
+ return (
+ <>
+ {is_need_tnc ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useNeedTNC', () => {
+ test('should be false if is_tnc_needed and is_eu are false and does not have an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: false,
+ is_eu: false,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('false');
+ });
+
+ test('should be false if is_tnc_needed is false but is_eu is true and does not have an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: false,
+ is_eu: true,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('false');
+ });
+
+ test('should be false if is_tnc_needed and is_eu are false but has an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: false,
+ is_eu: true,
+ mt5_login_list: [
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('false');
+ });
+
+ test('should be true if is_tnc_needed is true and is_eu is false but has an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: true,
+ is_eu: false,
+ mt5_login_list: [
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('true');
+ });
+
+ test('should be true if is_tnc_needed and is_eu are true and does not have an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: true,
+ is_eu: true,
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('true');
+ });
+
+ test('should be true if is_tnc_needed and is_eu are true and has an real STP account', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ is_tnc_needed: true,
+ is_eu: true,
+ mt5_login_list: [
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const is_need_tnc = screen.getByTestId('dt_is_need_tnc');
+ expect(is_need_tnc).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useRealSTPAccount.spec.tsx b/packages/cashier/src/hooks/__tests__/useRealSTPAccount.spec.tsx
new file mode 100644
index 000000000000..5fb155671059
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useRealSTPAccount.spec.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider } from '../useStore';
+import useRealSTPAccount from '../useRealSTPAccount';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseRealSTPAccountExample = () => {
+ const has_real_stp_account = useRealSTPAccount();
+
+ return (
+ <>
+ {has_real_stp_account ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useRealSTPAccount', () => {
+ test('should be false if does not have an account type of real with sub account type of financial_stp', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial_stp',
+ },
+ {
+ account_type: 'real',
+ sub_account_type: 'financial',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const has_real_stp_account = screen.getByTestId('dt_has_real_stp_account');
+ expect(has_real_stp_account).toHaveTextContent('false');
+ });
+
+ test('should be true if has an account type of real with sub account type of financial_stp', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ mt5_login_list: [
+ {
+ account_type: 'demo',
+ sub_account_type: 'financial',
+ },
+ {
+ account_type: 'real',
+ sub_account_type: 'financial_stp',
+ },
+ ],
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const has_real_stp_account = screen.getByTestId('dt_has_real_stp_account');
+ expect(has_real_stp_account).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useStore.spec.tsx b/packages/cashier/src/hooks/__tests__/useStore.spec.tsx
new file mode 100644
index 000000000000..4d3619c04270
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useStore.spec.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen } from '@testing-library/react';
+import { StoreProvider, useStore } from '../useStore';
+import { TRootStore, DeepPartial } from '../../types';
+
+const UseStoreExample = () => {
+ const store = useStore();
+
+ return (
+ <>
+ {store.client.email}
+ {store.ui.is_dark_mode_on ? 'true' : 'false'}
+ >
+ );
+};
+
+describe('useStore', () => {
+ test('should throw an error if StoreContext has not been provided', async () => {
+ expect(() => render()).toThrowError('useStore must be used within StoreContext');
+ });
+
+ test('should be able to access store data if StoreContext has been provided', async () => {
+ const mockRootStore: DeepPartial = {
+ client: {
+ email: 'john@company.com',
+ },
+ ui: {
+ is_dark_mode_on: true,
+ },
+ };
+
+ render(, {
+ wrapper: ({ children }) => {children},
+ });
+
+ const email = screen.getByTestId('dt_email');
+ const is_dark_mode_on = screen.getByTestId('dt_is_dark_mode_on');
+ expect(email).toHaveTextContent('john@company.com');
+ expect(is_dark_mode_on).toHaveTextContent('true');
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useSubscription.spec.tsx b/packages/cashier/src/hooks/__tests__/useSubscription.spec.tsx
new file mode 100644
index 000000000000..53b6636f0094
--- /dev/null
+++ b/packages/cashier/src/hooks/__tests__/useSubscription.spec.tsx
@@ -0,0 +1,85 @@
+import * as React from 'react';
+// Todo: After upgrading to react 18 we should use @testing-library/react-hooks instead.
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useWS as useWSShared } from '@deriv/shared';
+import useSubscription from '../useSubscription';
+import { TSocketRequestProps, TSocketSubscribableEndpointNames } from '../../types';
+
+jest.mock('@deriv/shared');
+
+const mockUseWSShared = useWSShared as jest.MockedFunction;
+
+const UseSubscriptionExample = ({
+ name,
+ request,
+}: {
+ name: T;
+ request?: TSocketRequestProps;
+}) => {
+ const WS = useSubscription(name);
+
+ return (
+
+ {WS.is_loading ? 'true' : 'false'}
+ {WS.error ? JSON.stringify(WS.error) : 'undefined'}
+ {WS.data ? JSON.stringify(WS.data) : 'undefined'}
+
+
+ );
+};
+
+describe('useSubscription', () => {
+ test('should subscribe to p2p_order_info and get the order updates', async () => {
+ mockUseWSShared.mockReturnValue({
+ subscribe: jest.fn(() => {
+ return {
+ subscribe: async (onData, onError) => {
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+ await delay(500);
+
+ onData({ p2p_order_info: { status: 'pending' } });
+
+ await delay(500);
+
+ onData({ p2p_order_info: { status: 'buyer-confirmed' } });
+
+ await delay(500);
+
+ onData({ p2p_order_info: { status: 'disputed' } });
+
+ await delay(500);
+
+ onError({ error: { code: 'Foo', message: 'Error message' } });
+
+ await delay(500);
+
+ onData({ p2p_order_info: { status: 'completed' } });
+
+ return { unsubscribe: () => Promise.resolve() };
+ },
+ };
+ }),
+ });
+
+ render();
+
+ const is_loading = screen.getByTestId('dt_is_loading');
+ const error = screen.getByTestId('dt_error');
+ const data = screen.getByTestId('dt_data');
+ const subscribe = screen.getByTestId('dt_subscribe');
+
+ expect(is_loading).toHaveTextContent('false');
+ expect(error).toHaveTextContent('undefined');
+ expect(data).toHaveTextContent('undefined');
+ userEvent.click(subscribe);
+ await waitFor(() => expect(data).toHaveTextContent('{"status":"pending"}'));
+ await waitFor(() => expect(data).toHaveTextContent('{"status":"buyer-confirmed"}'));
+ await waitFor(() => expect(data).toHaveTextContent('{"status":"disputed"}'));
+ await waitFor(() => expect(error).toHaveTextContent('{"code":"Foo","message":"Error message"}'));
+ await waitFor(() => expect(data).toHaveTextContent('{"status":"completed"}'));
+ });
+});
diff --git a/packages/cashier/src/hooks/__tests__/useWS.test.tsx b/packages/cashier/src/hooks/__tests__/useWS.spec.tsx
similarity index 64%
rename from packages/cashier/src/hooks/__tests__/useWS.test.tsx
rename to packages/cashier/src/hooks/__tests__/useWS.spec.tsx
index 3f71b69a7a99..fe6ec1d86138 100644
--- a/packages/cashier/src/hooks/__tests__/useWS.test.tsx
+++ b/packages/cashier/src/hooks/__tests__/useWS.spec.tsx
@@ -4,7 +4,7 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useWS as useWSShared } from '@deriv/shared';
import useWS from '../useWS';
-import { TSocketEndpointNames, TSocketRequestProps } from '../../types';
+import { TSocketEndpointNames, TSocketRequestProps, TSocketResponse } from '../../types';
jest.mock('@deriv/shared');
@@ -46,7 +46,13 @@ describe('useWS', () => {
test('should call ping and get pong in response', async () => {
mockUseWSShared.mockReturnValue({
- send: jest.fn(() => Promise.resolve({ ping: 'pong' })),
+ send: jest.fn(() =>
+ Promise.resolve>({
+ msg_type: 'ping',
+ ping: 'pong',
+ echo_req: {},
+ })
+ ),
});
render();
@@ -68,7 +74,13 @@ describe('useWS', () => {
test('should call verify_email and get 1 in response', async () => {
mockUseWSShared.mockReturnValue({
- send: jest.fn(() => Promise.resolve({ verify_email: 1 })),
+ send: jest.fn(() =>
+ Promise.resolve>({
+ verify_email: 1,
+ msg_type: 'verify_email',
+ echo_req: {},
+ })
+ ),
});
render(
@@ -90,5 +102,34 @@ describe('useWS', () => {
await waitFor(() => expect(is_loading).toHaveTextContent('false'));
});
- // TODO: Add more test cases.
+ test('should call cashier and get ASK_TNC_APPROVAL error code in response', async () => {
+ mockUseWSShared.mockReturnValue({
+ send: jest.fn(() =>
+ Promise.resolve>({
+ msg_type: 'cashier',
+ echo_req: {},
+ error: {
+ code: 'ASK_TNC_APPROVAL',
+ message: 'Error message',
+ },
+ })
+ ),
+ });
+
+ render();
+
+ const is_loading = screen.getByTestId('dt_is_loading');
+ const error = screen.getByTestId('dt_error');
+ const data = screen.getByTestId('dt_data');
+ const send = screen.getByTestId('dt_send');
+
+ expect(is_loading).toHaveTextContent('false');
+ expect(error).toHaveTextContent('undefined');
+ expect(data).toHaveTextContent('undefined');
+ userEvent.click(send);
+ await waitFor(() => expect(is_loading).toHaveTextContent('true'));
+ await waitFor(() => expect(data).toHaveTextContent('undefined'));
+ await waitFor(() => expect(error).toHaveTextContent('{"code":"ASK_TNC_APPROVAL","message":"Error message"}'));
+ await waitFor(() => expect(is_loading).toHaveTextContent('false'));
+ });
});
diff --git a/packages/cashier/src/hooks/index.ts b/packages/cashier/src/hooks/index.ts
index 4ed6c4b7c354..729085c341e8 100644
--- a/packages/cashier/src/hooks/index.ts
+++ b/packages/cashier/src/hooks/index.ts
@@ -1,4 +1,10 @@
export * from './useStore';
export { default as useCountdown } from './useCountdown';
export { default as useWS } from './useWS';
+export { default as useSubscription } from './useSubscription';
export { default as useVerifyEmail } from './useVerifyEmail';
+export { default as useNeedAuthentication } from './useNeedAuthentication';
+export { default as useNeedFinancialAssessment } from './useNeedFinancialAssessment';
+export { default as useRealSTPAccount } from './useRealSTPAccount';
+export { default as useNeedTNC } from './useNeedTNC';
+export { default as useDepositLocked } from './useDepositLocked';
diff --git a/packages/cashier/src/hooks/useDepositLocked.ts b/packages/cashier/src/hooks/useDepositLocked.ts
new file mode 100644
index 000000000000..75282fc17181
--- /dev/null
+++ b/packages/cashier/src/hooks/useDepositLocked.ts
@@ -0,0 +1,17 @@
+import useNeedAuthentication from './useNeedAuthentication';
+import useNeedFinancialAssessment from './useNeedFinancialAssessment';
+import useNeedTNC from './useNeedTNC';
+import { useStore } from './useStore';
+
+const useDepositLocked = () => {
+ const { client } = useStore();
+ const { is_deposit_lock } = client;
+ const is_need_authentication = useNeedAuthentication();
+ const is_need_tnc = useNeedTNC();
+ const is_need_financial_assessment = useNeedFinancialAssessment();
+ const is_deposit_locked = is_deposit_lock || is_need_authentication || is_need_tnc || is_need_financial_assessment;
+
+ return is_deposit_locked;
+};
+
+export default useDepositLocked;
diff --git a/packages/cashier/src/hooks/useNeedAuthentication.ts b/packages/cashier/src/hooks/useNeedAuthentication.ts
new file mode 100644
index 000000000000..43879816774c
--- /dev/null
+++ b/packages/cashier/src/hooks/useNeedAuthentication.ts
@@ -0,0 +1,11 @@
+import { useStore } from './useStore';
+
+const useNeedAuthentication = () => {
+ const { client } = useStore();
+ const { is_authentication_needed, is_eu } = client;
+ const is_need_authentication = is_authentication_needed && is_eu;
+
+ return is_need_authentication;
+};
+
+export default useNeedAuthentication;
diff --git a/packages/cashier/src/hooks/useNeedFinancialAssessment.ts b/packages/cashier/src/hooks/useNeedFinancialAssessment.ts
new file mode 100644
index 000000000000..8b88bde779c5
--- /dev/null
+++ b/packages/cashier/src/hooks/useNeedFinancialAssessment.ts
@@ -0,0 +1,12 @@
+import { useStore } from './useStore';
+
+const useNeedFinancialAssessment = () => {
+ const { client } = useStore();
+ const { is_financial_account, is_financial_information_incomplete, is_trading_experience_incomplete } = client;
+ const is_need_financial_assessment =
+ is_financial_account && (is_financial_information_incomplete || is_trading_experience_incomplete);
+
+ return is_need_financial_assessment;
+};
+
+export default useNeedFinancialAssessment;
diff --git a/packages/cashier/src/hooks/useNeedTNC.ts b/packages/cashier/src/hooks/useNeedTNC.ts
new file mode 100644
index 000000000000..d1a077d0e68f
--- /dev/null
+++ b/packages/cashier/src/hooks/useNeedTNC.ts
@@ -0,0 +1,13 @@
+import useRealSTPAccount from './useRealSTPAccount';
+import { useStore } from './useStore';
+
+const useNeedTNC = () => {
+ const { client } = useStore();
+ const { is_eu, is_tnc_needed } = client;
+ const has_real_stp_account = useRealSTPAccount();
+ const is_need_tnc = (is_eu || has_real_stp_account) && is_tnc_needed;
+
+ return is_need_tnc;
+};
+
+export default useNeedTNC;
diff --git a/packages/cashier/src/hooks/useRealSTPAccount.ts b/packages/cashier/src/hooks/useRealSTPAccount.ts
new file mode 100644
index 000000000000..3e8ecf88eeb7
--- /dev/null
+++ b/packages/cashier/src/hooks/useRealSTPAccount.ts
@@ -0,0 +1,13 @@
+import { useStore } from './useStore';
+
+const useRealSTPAccount = () => {
+ const { client } = useStore();
+ const { mt5_login_list } = client;
+ const has_real_stp_account = mt5_login_list.some(
+ item => item.account_type === 'real' && item.sub_account_type === 'financial_stp'
+ );
+
+ return has_real_stp_account;
+};
+
+export default useRealSTPAccount;
diff --git a/packages/cashier/src/hooks/useSubscription.ts b/packages/cashier/src/hooks/useSubscription.ts
new file mode 100644
index 000000000000..ac1340add3a3
--- /dev/null
+++ b/packages/cashier/src/hooks/useSubscription.ts
@@ -0,0 +1,44 @@
+import { TSocketRequestProps, TSocketResponseData, TSocketSubscribableEndpointNames } from 'Types';
+import { useWS as useWSShared } from '@deriv/shared';
+import { useState } from 'react';
+
+const useSubscription = (name: T) => {
+ const [is_loading, setIsLoading] = useState(false);
+ const [is_subscribed, setSubscribed] = useState(false);
+ const [error, setError] = useState();
+ const [data, setData] = useState>();
+ const [subscriber, setSubscriber] = useState<{ unsubscribe?: VoidFunction }>();
+ const WS = useWSShared();
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const onData = (response: any) => {
+ setData(response[name]);
+ setIsLoading(false);
+ };
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const onError = (response: any) => {
+ setError(response.error);
+ setIsLoading(false);
+ };
+
+ const subscribe = (...props: TSocketRequestProps extends never ? [undefined?] : [TSocketRequestProps]) => {
+ setIsLoading(true);
+ setSubscribed(true);
+
+ try {
+ setSubscriber(WS.subscribe({ [name]: 1, subscribe: 1, ...(props[0] || {}) }).subscribe(onData, onError));
+ } catch (e) {
+ setError(e);
+ }
+ };
+
+ const unsubscribe = () => {
+ subscriber?.unsubscribe?.();
+ setSubscribed(false);
+ };
+
+ return { subscribe, unsubscribe, is_loading, is_subscribed, error, data };
+};
+
+export default useSubscription;
diff --git a/packages/cashier/src/hooks/useWS.ts b/packages/cashier/src/hooks/useWS.ts
index 8a6e36313fe0..39511a999d8d 100644
--- a/packages/cashier/src/hooks/useWS.ts
+++ b/packages/cashier/src/hooks/useWS.ts
@@ -14,7 +14,7 @@ const useWS = (name: T) => {
setIsLoading(true);
try {
- const response = await WS.send({ [name]: 1, ...props });
+ const response = await WS.send({ [name]: 1, ...(props[0] || {}) });
if (response.error) {
setError(response.error);
diff --git a/packages/cashier/src/types/types.ts b/packages/cashier/src/types/types.ts
index 64d39082a20b..5031a516ef7a 100644
--- a/packages/cashier/src/types/types.ts
+++ b/packages/cashier/src/types/types.ts
@@ -9,6 +9,24 @@ import {
P2POrderInformationResponse,
ServerStatusRequest,
ServerStatusResponse,
+ TermsAndConditionsApprovalRequest,
+ TermsAndConditionsApprovalResponse,
+ CashierInformationRequest,
+ CashierInformationResponse,
+ CryptocurrencyConfigurationsRequest,
+ CryptocurrencyConfigurationsResponse,
+ PaymentAgentTransferRequest,
+ PaymentAgentTransferResponse,
+ PaymentAgentListRequest,
+ PaymentAgentListResponse,
+ PaymentAgentDetailsRequest,
+ PaymentAgentDetailsResponse,
+ PaymentAgentWithdrawRequest,
+ PaymentAgentWithdrawResponse,
+ TransferBetweenAccountsRequest,
+ TransferBetweenAccountsResponse,
+ BalanceRequest,
+ BalanceResponse,
} from '@deriv/api-types';
import { KeysMatching } from './utils';
@@ -33,20 +51,61 @@ export type TSocketEndpoints = {
request: ServerStatusRequest;
response: ServerStatusResponse;
};
+ tnc_approval: {
+ request: TermsAndConditionsApprovalRequest;
+ response: TermsAndConditionsApprovalResponse;
+ };
+ cashier: {
+ request: CashierInformationRequest;
+ response: CashierInformationResponse;
+ };
+ crypto_config: {
+ request: CryptocurrencyConfigurationsRequest;
+ response: CryptocurrencyConfigurationsResponse;
+ };
+ paymentagent_transfer: {
+ request: PaymentAgentTransferRequest;
+ response: PaymentAgentTransferResponse;
+ };
+ paymentagent_list: {
+ request: PaymentAgentListRequest;
+ response: PaymentAgentListResponse;
+ };
+ paymentagent_details: {
+ request: PaymentAgentDetailsRequest;
+ response: PaymentAgentDetailsResponse;
+ };
+ paymentagent_withdraw: {
+ request: PaymentAgentWithdrawRequest;
+ response: PaymentAgentWithdrawResponse;
+ };
+ transfer_between_accounts: {
+ request: TransferBetweenAccountsRequest;
+ response: TransferBetweenAccountsResponse;
+ };
+ balance: {
+ request: BalanceRequest;
+ response: BalanceResponse;
+ };
};
export type TSocketEndpointNames = keyof TSocketEndpoints;
+
export type TSocketSubscribableEndpointNames =
| KeysMatching
| 'exchange_rates';
+
export type TSocketResponse = TSocketEndpoints[T]['response'];
+
export type TSocketResponseData = TSocketResponse[T];
-export type TSocketResponseSubscriptionInformation = TSocketResponse['subscription'];
+
export type TSocketRequest = TSocketEndpoints[T]['request'];
+
type TSocketRequestCleaned = Omit<
TSocketRequest,
- (T extends KeysMatching, 1> ? T : never) | 'passthrough' | 'req_id'
+ (T extends KeysMatching, 1> ? T : never) | 'passthrough' | 'req_id' | 'subscribe'
>;
+
export type TSocketRequestProps = TSocketRequestCleaned extends Record
? never
: TSocketRequestCleaned;