{
})}
ref={traders_hub_ref}
>
- {has_any_real_account && is_real &&
}
+ {has_any_real_account &&
}
{should_show_banner && !has_any_real_account && !is_eu && is_landing_company_loaded && (
}>
diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js
index cfc55a8997e1..aeb44ebe458a 100644
--- a/packages/appstore/webpack.config.js
+++ b/packages/appstore/webpack.config.js
@@ -42,6 +42,10 @@ const svg_loaders = [
// const default_plugins = [new CleanWebpackPlugin(), new ForkTsCheckerWebpackPlugin()];
const default_plugins = [
// new BundleAnalyzerPlugin(),
+ new Dotenv(),
+ new DefinePlugin({
+ 'process.env.TRUSTPILOT_API_KEY': JSON.stringify(process.env.TRUSTPILOT_API_KEY),
+ }),
new IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }),
new CircularDependencyPlugin({ exclude: /node_modules/, failOnError: true }),
];
@@ -87,12 +91,6 @@ module.exports = function (env) {
},
extensions: ['.ts', '.tsx', '.js'],
},
- plugins: [
- new Dotenv(),
- new DefinePlugin({
- 'process.env.TRUSTPILOT_API_KEY': JSON.stringify(process.env.TRUSTPILOT_API_KEY),
- }),
- ],
module: {
rules: [
{
diff --git a/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_getSublist.js b/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_getSublist.js
index 79f146aa934f..788baa58eb04 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_getSublist.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_getSublist.js
@@ -83,8 +83,7 @@ Blockly.Blocks.lists_getSublist = {
}
this.initSvg();
- //commented this line breaks the backward compatibility
- //this.render(false);
+ this.renderEfficiently();
},
customContextMenu(menu) {
modifyContextMenu(menu);
diff --git a/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_split.js b/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_split.js
index 267a3c155690..6b5941005529 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_split.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Advanced/List/lists_split.js
@@ -69,7 +69,7 @@ Blockly.Blocks.lists_split = {
}
this.initSvg();
- //this.render(false);
+ this.renderEfficiently();
},
customContextMenu(menu) {
modifyContextMenu(menu);
diff --git a/packages/bot-skeleton/src/scratch/blocks/Math/math_number_property.js b/packages/bot-skeleton/src/scratch/blocks/Math/math_number_property.js
index 34b898aeddeb..0ffd8a5bd9a0 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Math/math_number_property.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Math/math_number_property.js
@@ -65,18 +65,15 @@ Blockly.Blocks.math_number_property = {
return container;
},
updateShape(hasDivisorInput) {
- const inputExists = this.getInput('DIVISOR');
-
if (hasDivisorInput) {
- if (!inputExists) {
+ const inputExists = this.getInput('DIVISOR');
+ if (inputExists) {
+ this.removeInput('DIVISOR');
+ } else {
this.appendValueInput('DIVISOR').setCheck('Number');
this.initSvg();
- //commented this line breaks the backward compatibility
- //this.render(false);
+ this.renderEfficiently();
}
- } else {
- //commented this line breaks the backward compatibiliy
- //this.removeInput('DIVISOR');
}
},
customContextMenu(menu) {
diff --git a/packages/bot-skeleton/src/scratch/blocks/Text/text_charAt.js b/packages/bot-skeleton/src/scratch/blocks/Text/text_charAt.js
index be23d3ef997e..0c2dd78fac7f 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Text/text_charAt.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Text/text_charAt.js
@@ -76,8 +76,7 @@ Blockly.Blocks.text_charAt = {
this.isAt = isAt;
this.initSvg();
- //commented this line breaks the backward compatibility
- //this.render(false);
+ this.renderEfficiently();
},
getRequiredValueInputs() {
return {
diff --git a/packages/bot-skeleton/src/scratch/blocks/Text/text_getSubstring.js b/packages/bot-skeleton/src/scratch/blocks/Text/text_getSubstring.js
index 88524074ba31..11556acec4ef 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Text/text_getSubstring.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Text/text_getSubstring.js
@@ -131,8 +131,7 @@ Blockly.Blocks.text_getSubstring = {
}
this.initSvg();
- //commented this line breaks the backward compatibility
- //this.render(false);
+ this.renderEfficiently();
},
getRequiredValueInputs() {
const hasInput = input_name => this.getInput(input_name)?.type === Blockly.INPUT_VALUE;
diff --git a/packages/bot-skeleton/src/scratch/blocks/Text/text_prompt_ext.js b/packages/bot-skeleton/src/scratch/blocks/Text/text_prompt_ext.js
index b5627e34e0d1..76565d8aef07 100755
--- a/packages/bot-skeleton/src/scratch/blocks/Text/text_prompt_ext.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Text/text_prompt_ext.js
@@ -12,8 +12,7 @@ Blockly.Blocks.text_prompt_ext = {
this.setOutput(true, 'Number');
}
this.initSvg();
- //commented this line breaks the backward compatibility
- //this.render(false);
+ this.renderEfficiently();
return undefined;
});
},
diff --git a/packages/bot-skeleton/src/scratch/hooks/gesture.js b/packages/bot-skeleton/src/scratch/hooks/gesture.js
index 487e45bde6c9..aa3a74e86f65 100644
--- a/packages/bot-skeleton/src/scratch/hooks/gesture.js
+++ b/packages/bot-skeleton/src/scratch/hooks/gesture.js
@@ -24,6 +24,7 @@ Blockly.Gesture.prototype.updateIsDraggingFromFlyout = function () {
}
// The start block is no longer relevant, because this is a drag.
+ this.startBlock.workspace.clearGesture();
this.startBlock = null;
this.targetBlock = this.flyout.createBlock(this.mostRecentEvent, this.targetBlock);
this.targetBlock.select();
diff --git a/packages/bot-web-ui/package.json b/packages/bot-web-ui/package.json
index cf001c18b81a..c59394543f95 100644
--- a/packages/bot-web-ui/package.json
+++ b/packages/bot-web-ui/package.json
@@ -77,7 +77,7 @@
"@deriv/bot-skeleton": "^1.0.0",
"@deriv/components": "^1.0.0",
"@deriv/hooks": "^1.0.0",
- "@deriv/deriv-charts": "^2.2.0",
+ "@deriv/deriv-charts": "^2.3.0",
"@deriv/shared": "^1.0.0",
"@deriv/stores": "^1.0.0",
"@deriv/translations": "^1.0.0",
diff --git a/packages/bot-web-ui/src/analytics/__tests__/utils.spec.ts b/packages/bot-web-ui/src/analytics/__tests__/utils.spec.ts
index 069ddb5f1935..e1eb8fcab98c 100644
--- a/packages/bot-web-ui/src/analytics/__tests__/utils.spec.ts
+++ b/packages/bot-web-ui/src/analytics/__tests__/utils.spec.ts
@@ -1,14 +1,26 @@
-import { DBOT_TABS } from 'Constants/bot-contents';
-import { TFormStrategy } from '../constants';
+import { Analytics } from '@deriv-com/analytics';
+import { ACTION, TFormStrategy } from '../constants';
+import { rudderStackSendSwitchLoadStrategyTabEvent } from '../rudderstack-bot-builder';
+import {
+ rudderStackSendGoogleDriveConnectEvent,
+ rudderStackSendGoogleDriveDisconnectEvent,
+ rudderStackSendUploadStrategyCompletedEvent,
+} from '../rudderstack-common-events';
import {
- getTradeParameterData,
getRsDropdownTextFromLocalStorage,
- getSubpageName,
+ getStrategyType,
+ getTradeParameterData,
rudderstack_text_error,
} from '../utils';
jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn());
+jest.mock('@deriv-com/analytics', () => ({
+ Analytics: {
+ trackEvent: jest.fn(),
+ },
+}));
+
describe('utils', () => {
const form_strategy = {
form_values: {
@@ -19,13 +31,6 @@ describe('utils', () => {
},
} as TFormStrategy;
- const subpage_name = {
- [`${DBOT_TABS.DASHBOARD}`]: 'dashboard',
- [`${DBOT_TABS.BOT_BUILDER}`]: 'bot_builder',
- [`${DBOT_TABS.CHART}`]: 'charts',
- [`${DBOT_TABS.TUTORIAL}`]: 'tutorials',
- };
-
it('getRsDropdownTextFromLocalStorage() should return empty object when parced json of "qs-analytics" localStorage equals undefined or null', () => {
const result = getRsDropdownTextFromLocalStorage();
expect(result).toEqual({});
@@ -83,45 +88,111 @@ describe('utils', () => {
});
});
- it('getSubpageName() should "undefined" if was not pass anything', () => {
- const result = getSubpageName();
+ it('should return "new" if the XML has is_dbot="true"', () => {
+ const xml = `
+
+
+ `;
- expect(result).toEqual('undefined');
+ const result = getStrategyType(xml);
+ expect(result).toBe('new');
});
- it('getSubpageName() should return "dashboard" active_tab equals 0', () => {
- // eslint-disable-next-line no-proto
- jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(`${DBOT_TABS.DASHBOARD}`);
+ it('should return "old" if the XML has is_dbot="false"', () => {
+ const xml = `
+
+
+ `;
- const result = getSubpageName();
+ const result = getStrategyType(xml);
+ expect(result).toBe('old');
+ });
- expect(result).toEqual(subpage_name[`${DBOT_TABS.DASHBOARD}`]);
+ it('should return "old" if the XML does not contain the is_dbot attribute', () => {
+ const xml = `
+
+
+ `;
+ const result = getStrategyType(xml);
+ expect(result).toBe('old');
});
- it('getSubpageName() should return "bot_builder" active_tab equals 1', () => {
- // eslint-disable-next-line no-proto
- jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(`${DBOT_TABS.BOT_BUILDER}`);
+ it('should return "old" for invalid XML', () => {
+ const xml = `
+
+
+ `;
+
+ const result = getStrategyType(xml);
+ expect(result).toBe('old');
+ });
+
+ it('should return "old" if an unexpected error occurs during parsing', () => {
+ global.DOMParser = jest.fn().mockImplementation(() => {
+ return {
+ parseFromString: () => {
+ throw new Error('Parsing error');
+ },
+ };
+ });
- const result = getSubpageName();
+ const xml = '
';
+ const result = getStrategyType(xml);
- expect(result).toEqual(subpage_name[`${DBOT_TABS.BOT_BUILDER}`]);
+ expect(result).toBe('old');
});
- it('getSubpageName() should return "charts" active_tab equals 2', () => {
- // eslint-disable-next-line no-proto
- jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(`${DBOT_TABS.CHART}`);
+ it('should call Analytics.trackEvent with the correct parameters', () => {
+ const load_strategy = { load_strategy_tab: 'local' };
- const result = getSubpageName();
+ rudderStackSendSwitchLoadStrategyTabEvent(load_strategy);
- expect(result).toEqual(subpage_name[`${DBOT_TABS.CHART}`]);
+ expect(Analytics.trackEvent).toHaveBeenCalledWith('ce_bot_form', {
+ action: ACTION.SWITCH_LOAD_STRATEGY_TAB,
+ form_name: 'ce_bot_form',
+ load_strategy_tab: load_strategy.load_strategy_tab,
+ subform_name: 'load_strategy',
+ subpage_name: 'bot_builder',
+ });
});
- it('getSubpageName() should return "tutorials" active_tab equals 3', () => {
- // eslint-disable-next-line no-proto
- jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(`${DBOT_TABS.TUTORIAL}`);
+ it('should call Analytics.trackEvent with correct parameters for upload strategy completed event', () => {
+ const upload_parameters = {
+ upload_provider: 'my_computer',
+ upload_id: '12345',
+ upload_type: 'new',
+ };
+
+ rudderStackSendUploadStrategyCompletedEvent(upload_parameters);
+
+ expect(Analytics.trackEvent).toHaveBeenCalledWith('ce_bot_form', {
+ action: ACTION.UPLOAD_STRATEGY_COMPLETED,
+ form_name: 'ce_bot_form',
+ subform_name: 'load_strategy',
+ subpage_name: 'bot_builder',
+ upload_provider: upload_parameters.upload_provider,
+ upload_id: upload_parameters.upload_id,
+ upload_type: upload_parameters.upload_type,
+ });
+ });
- const result = getSubpageName();
+ it('should call Analytics.trackEvent with correct parameters for Google Drive connect event', () => {
+ rudderStackSendGoogleDriveConnectEvent();
- expect(result).toEqual(subpage_name[`${DBOT_TABS.TUTORIAL}`]);
+ expect(Analytics.trackEvent).toHaveBeenCalledWith('ce_bot_form', {
+ action: ACTION.GOOGLE_DRIVE_CONNECT,
+ form_name: 'ce_bot_form',
+ subpage_name: 'bot_builder',
+ });
+ });
+
+ it('should call Analytics.trackEvent with correct parameters for Google Drive disconnect event', () => {
+ rudderStackSendGoogleDriveDisconnectEvent();
+
+ expect(Analytics.trackEvent).toHaveBeenCalledWith('ce_bot_form', {
+ action: ACTION.GOOGLE_DRIVE_DISCONNECT,
+ form_name: 'ce_bot_form',
+ subpage_name: 'bot_builder',
+ });
});
});
diff --git a/packages/bot-web-ui/src/analytics/rudderstack-bot-builder.ts b/packages/bot-web-ui/src/analytics/rudderstack-bot-builder.ts
index 91b6038f8f01..b2dbe7705d82 100644
--- a/packages/bot-web-ui/src/analytics/rudderstack-bot-builder.ts
+++ b/packages/bot-web-ui/src/analytics/rudderstack-bot-builder.ts
@@ -1,6 +1,5 @@
import { Analytics, TEvents } from '@deriv-com/analytics';
import { ACTION, form_name } from './constants';
-import { getSubpageName } from './utils';
export const rudderStackSendSwitchLoadStrategyTabEvent = ({ load_strategy_tab }: TEvents['ce_bot_form']) => {
Analytics.trackEvent('ce_bot_form', {
@@ -8,6 +7,6 @@ export const rudderStackSendSwitchLoadStrategyTabEvent = ({ load_strategy_tab }:
form_name,
load_strategy_tab,
subform_name: 'load_strategy',
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
});
};
diff --git a/packages/bot-web-ui/src/analytics/rudderstack-common-events.ts b/packages/bot-web-ui/src/analytics/rudderstack-common-events.ts
index fd5ec1692383..7a58c1d82b45 100644
--- a/packages/bot-web-ui/src/analytics/rudderstack-common-events.ts
+++ b/packages/bot-web-ui/src/analytics/rudderstack-common-events.ts
@@ -1,14 +1,20 @@
import { Analytics, TEvents } from '@deriv-com/analytics';
import { ACTION, form_name, TFormStrategy } from './constants';
-import { getRsStrategyType, getSubpageName } from './utils';
+import { getRsStrategyType } from './utils';
-export const rudderStackSendOpenEvent = ({ subform_source, subform_name }: TEvents['ce_bot_form']) => {
+export const rudderStackSendOpenEvent = ({
+ subpage_name,
+ subform_source,
+ subform_name,
+ load_strategy_tab,
+}: TEvents['ce_bot_form']) => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.OPEN,
form_name,
- subpage_name: getSubpageName(),
+ subpage_name,
subform_name,
subform_source,
+ load_strategy_tab,
});
};
@@ -16,22 +22,23 @@ export const rudderStackSendCloseEvent = ({
subform_name,
quick_strategy_tab,
selected_strategy,
+ load_strategy_tab,
}: TEvents['ce_bot_form'] & TFormStrategy) => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.CLOSE,
form_name,
- subpage_name: getSubpageName(),
subform_name,
quick_strategy_tab,
strategy_name: getRsStrategyType(selected_strategy),
+ load_strategy_tab,
});
};
-export const rudderStackSendRunBotEvent = () => {
+export const rudderStackSendRunBotEvent = ({ subpage_name }: TEvents['ce_bot_form']) => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.RUN_BOT,
form_name,
- subpage_name: getSubpageName(),
+ subpage_name,
});
};
@@ -40,7 +47,7 @@ export const rudderStackSendUploadStrategyStartEvent = ({ upload_provider, uploa
action: ACTION.UPLOAD_STRATEGY_START,
form_name,
subform_name: 'load_strategy',
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
upload_provider,
upload_id,
});
@@ -55,7 +62,7 @@ export const rudderStackSendUploadStrategyCompletedEvent = ({
action: ACTION.UPLOAD_STRATEGY_COMPLETED,
form_name,
subform_name: 'load_strategy',
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
upload_provider,
upload_id,
upload_type,
@@ -73,7 +80,7 @@ export const rudderStackSendUploadStrategyFailedEvent = ({
action: ACTION.UPLOAD_STRATEGY_FAILED,
form_name,
subform_name: 'load_strategy',
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
upload_provider,
upload_id,
upload_type,
@@ -86,7 +93,7 @@ export const rudderStackSendGoogleDriveConnectEvent = () => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.GOOGLE_DRIVE_CONNECT,
form_name,
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
});
};
@@ -94,6 +101,6 @@ export const rudderStackSendGoogleDriveDisconnectEvent = () => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.GOOGLE_DRIVE_DISCONNECT,
form_name,
- subpage_name: getSubpageName(),
+ subpage_name: 'bot_builder',
});
};
diff --git a/packages/bot-web-ui/src/analytics/rudderstack-dashboard.ts b/packages/bot-web-ui/src/analytics/rudderstack-dashboard.ts
index d760faa6c473..840df02185de 100644
--- a/packages/bot-web-ui/src/analytics/rudderstack-dashboard.ts
+++ b/packages/bot-web-ui/src/analytics/rudderstack-dashboard.ts
@@ -1,11 +1,11 @@
import { Analytics, TEvents } from '@deriv-com/analytics';
import { ACTION, form_name } from './constants';
-export const rudderStackSendDashboardClickEvent = ({ dashboard_click_name }: TEvents['ce_bot_form']) => {
+export const rudderStackSendDashboardClickEvent = ({ dashboard_click_name, subpage_name }: TEvents['ce_bot_form']) => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.DASHBOARD_CLICK,
form_name,
- subpage_name: 'dashboard',
+ subpage_name,
dashboard_click_name,
});
};
diff --git a/packages/bot-web-ui/src/analytics/rudderstack-quick-strategy.ts b/packages/bot-web-ui/src/analytics/rudderstack-quick-strategy.ts
index ad37c56840ee..d3202f5252b1 100644
--- a/packages/bot-web-ui/src/analytics/rudderstack-quick-strategy.ts
+++ b/packages/bot-web-ui/src/analytics/rudderstack-quick-strategy.ts
@@ -1,6 +1,6 @@
import { Analytics, TEvents } from '@deriv-com/analytics';
import { ACTION, form_name, type TFormStrategy } from './constants';
-import { getRsStrategyType, getSubpageName, getTradeParameterData } from './utils';
+import { getRsStrategyType, getTradeParameterData } from './utils';
export const rudderStackSendQsRunStrategyEvent = ({
form_values,
diff --git a/packages/bot-web-ui/src/analytics/rudderstack-tutorials.ts b/packages/bot-web-ui/src/analytics/rudderstack-tutorials.ts
index 22ef7e730642..38f0857b0e8f 100644
--- a/packages/bot-web-ui/src/analytics/rudderstack-tutorials.ts
+++ b/packages/bot-web-ui/src/analytics/rudderstack-tutorials.ts
@@ -1,12 +1,12 @@
import { Analytics, TEvents } from '@deriv-com/analytics';
import { ACTION, form_name, TSelectedStrategy } from './constants';
-import { getRsStrategyType, getSubpageName } from './utils';
+import { getRsStrategyType } from './utils';
export const rudderStackSendSelectQsStrategyGuideEvent = ({ selected_strategy }: TSelectedStrategy) => {
Analytics.trackEvent('ce_bot_form', {
action: ACTION.SELECT_QUICK_STRATEGY_GUIDE,
form_name,
- subpage_name: getSubpageName(),
+ subpage_name: 'tutorials',
strategy_name: getRsStrategyType(selected_strategy),
});
};
@@ -15,7 +15,7 @@ export const rudderStackSendTutorialSearchEvent = ({ search_term }: TEvents['ce_
Analytics.trackEvent('ce_bot_form', {
action: 'search',
form_name: 'ce_bot_form',
- subpage_name: getSubpageName(),
+ subpage_name: 'tutorials',
search_term,
});
};
diff --git a/packages/bot-web-ui/src/analytics/utils.ts b/packages/bot-web-ui/src/analytics/utils.ts
index bc8cd82df6c0..e0f3f7f62a04 100644
--- a/packages/bot-web-ui/src/analytics/utils.ts
+++ b/packages/bot-web-ui/src/analytics/utils.ts
@@ -19,20 +19,6 @@ export const getRsStrategyType = (selected_strategy: string) => STRATEGIES[selec
export const getQsActiveTabString = (tab: string) => (tab === 'TRADE_PARAMETERS' ? 'trade parameters' : 'learn more');
-export const getSubpageName = () => {
- const active_tab = localStorage.getItem('active_tab');
- if (active_tab === '0') {
- return 'dashboard';
- } else if (active_tab === '1') {
- return 'bot_builder';
- } else if (active_tab === '2') {
- return 'charts';
- } else if (active_tab === '3') {
- return 'tutorials';
- }
- return 'undefined';
-};
-
enum LOAD_MODAL_TABS_VALUE {
recent = 'recent',
local = 'local',
diff --git a/packages/bot-web-ui/src/app/__tests__/app-content.spec.tsx b/packages/bot-web-ui/src/app/__tests__/app-content.spec.tsx
index de755b1d9e52..4cbb48f656e4 100644
--- a/packages/bot-web-ui/src/app/__tests__/app-content.spec.tsx
+++ b/packages/bot-web-ui/src/app/__tests__/app-content.spec.tsx
@@ -83,10 +83,10 @@ describe('AppContent', () => {
});
beforeAll(() => {
- mock_DBot_store = mockDBotStore(mock_store, mock_ws);
+ mock_DBot_store = mockDBotStore(mock_store, mock_ws as any);
wrapper = ({ children }: { children: JSX.Element }) => (
-
+
{children}
@@ -137,7 +137,7 @@ describe('AppContent', () => {
});
it('should unsubscribe message handler on component unmount', async () => {
- mock_store.client.is_logged_in = false;
+ mock_store.client.is_logged_in = true;
mock_DBot_store?.transactions?.recovered_transactions.push(11);
if (mock_DBot_store)
diff --git a/packages/bot-web-ui/src/app/app-content.jsx b/packages/bot-web-ui/src/app/app-content.jsx
index 62aa01b485dc..dca3d7799550 100644
--- a/packages/bot-web-ui/src/app/app-content.jsx
+++ b/packages/bot-web-ui/src/app/app-content.jsx
@@ -34,43 +34,50 @@ const AppContent = observer(() => {
const init_api_interval = React.useRef(null);
const msg_listener = React.useRef(null);
- const handleMessage = ({ data }) => {
- if (data?.msg_type === 'proposal_open_contract' && !data?.error) {
- const { proposal_open_contract } = data;
- if (
- proposal_open_contract?.status !== 'open' &&
- !recovered_transactions?.includes(proposal_open_contract?.contract_id)
- ) {
- recoverPendingContracts(proposal_open_contract);
+ const handleMessage = React.useCallback(
+ ({ data }) => {
+ if (data?.msg_type === 'proposal_open_contract' && !data?.error) {
+ const { proposal_open_contract } = data;
+ if (
+ proposal_open_contract?.status !== 'open' &&
+ !recovered_transactions?.includes(proposal_open_contract?.contract_id)
+ ) {
+ recoverPendingContracts(proposal_open_contract);
+ }
}
- }
- };
+ },
+ [recovered_transactions, recoverPendingContracts]
+ );
- function checkIfApiInitialized() {
+ const checkIfApiInitialized = React.useCallback(() => {
init_api_interval.current = setInterval(() => {
if (api_base?.api) {
clearInterval(init_api_interval.current);
+
// Listen for proposal open contract messages to check
// if there is any active contract from bot still running
if (api_base?.api && !is_subscribed_to_msg_listener.current) {
is_subscribed_to_msg_listener.current = true;
- msg_listener.current = api_base.api?.onMessage()?.subscribe(handleMessage);
+ msg_listener.current = api_base.api.onMessage()?.subscribe(handleMessage);
}
}
}, 500);
- }
+ }, [handleMessage]);
React.useEffect(() => {
- // Check until api is initialized and then subscribe to the proposal open conrtact
- checkIfApiInitialized();
+ // Check until api is initialized and then subscribe to the api messages
+ // Also we should only subscribe to the messages once user is logged in
+ // And is not already subscribed to the messages
+ if (!is_subscribed_to_msg_listener.current && client.is_logged_in) {
+ checkIfApiInitialized();
+ }
return () => {
if (is_subscribed_to_msg_listener.current && msg_listener.current) {
is_subscribed_to_msg_listener.current = false;
msg_listener.current.unsubscribe();
}
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ }, [checkIfApiInitialized, client.is_logged_in, client.loginid]);
React.useEffect(() => {
showDigitalOptionsMaltainvestError(client, common);
diff --git a/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-loading.spec.tsx b/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-loading.spec.tsx
index 32e8c9180af1..f1dbbcc0c7ad 100644
--- a/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-loading.spec.tsx
+++ b/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-loading.spec.tsx
@@ -4,13 +4,10 @@ import { render, screen } from '@testing-library/react';
import { mock_ws } from 'Utils/mock';
import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore';
import ContractCardLoader from '../contract-card-loading';
+import { contract_stages } from 'Constants/contract-stage';
+import { message_running_bot } from '../contract-card-running-bot';
-jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn());
-jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({
- saveRecentWorkspace: jest.fn(),
- unHighlightAllBlocks: jest.fn(),
-}));
-jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn());
+jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({}));
describe('ContractCardLoader', () => {
let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element;
@@ -53,4 +50,12 @@ describe('ContractCardLoader', () => {
const stopElement = svgElement.querySelector('animate'); // accessing the node directly to test the animation duration
expect(stopElement).toHaveAttribute('dur', '5s');
});
+
+ it('should render ContractCardLoader with the Text component when contract stage is running bot', () => {
+ render(
, {
+ wrapper,
+ });
+ const text = screen.getByText(message_running_bot);
+ expect(text).toBeInTheDocument();
+ });
});
diff --git a/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-running-bot.spec.tsx b/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-running-bot.spec.tsx
new file mode 100644
index 000000000000..d5f24c14aeaa
--- /dev/null
+++ b/packages/bot-web-ui/src/components/contract-card-loading/__tests__/contract-card-running-bot.spec.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { mockStore, StoreProvider } from '@deriv/stores';
+import { render, screen } from '@testing-library/react';
+import { mock_ws } from 'Utils/mock';
+import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore';
+import ContractCardRunningBot, { message_running_bot } from '../contract-card-running-bot';
+
+jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({}));
+
+describe('ContractCardRunningBot', () => {
+ let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element;
+ beforeAll(() => {
+ const mock_store = mockStore({});
+ const mock_DBot_store = mockDBotStore(mock_store, mock_ws);
+
+ wrapper = ({ children }: { children: JSX.Element }) => (
+
+
+ {children}
+
+
+ );
+ });
+
+ it('renders ContractCardRunningBot with the Icon component with correct props', () => {
+ const { container } = render(
);
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
+ const svg = container.getElementsByTagName('svg')[0];
+ expect(svg).toBeInTheDocument();
+ expect(svg).toHaveAttribute('id', 'rotate-icon');
+ expect(svg).toHaveClass('dc-icon--black');
+ expect(svg).toHaveAttribute('height', '16');
+ expect(svg).toHaveAttribute('width', '16');
+ });
+
+ it('renders ContractCardRunningBot with the Text component with the correct text and styles', () => {
+ render(
);
+ const text = screen.getByText(message_running_bot);
+ expect(text).toBeInTheDocument();
+ expect(text).toHaveClass('dc-contract-card-message');
+ });
+});
diff --git a/packages/bot-web-ui/src/components/contract-card-loading/contract-card-loading.tsx b/packages/bot-web-ui/src/components/contract-card-loading/contract-card-loading.tsx
index fe9d03e014e6..cded20044fcb 100644
--- a/packages/bot-web-ui/src/components/contract-card-loading/contract-card-loading.tsx
+++ b/packages/bot-web-ui/src/components/contract-card-loading/contract-card-loading.tsx
@@ -1,35 +1,44 @@
import React from 'react';
import ContentLoader from 'react-content-loader';
+import { contract_stages } from 'Constants/contract-stage';
+import ContractCardRunningBot from './contract-card-running-bot';
type TContractCardLoader = {
speed?: number;
+ contract_stage?: number;
};
-const ContractCardLoader = ({ speed = 3 }: TContractCardLoader) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+const ContractCardLoader = ({ speed = 3, contract_stage }: TContractCardLoader) => (
+ <>
+ {contract_stage === contract_stages.RUNNING ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
);
export default ContractCardLoader;
diff --git a/packages/bot-web-ui/src/components/contract-card-loading/contract-card-running-bot.tsx b/packages/bot-web-ui/src/components/contract-card-loading/contract-card-running-bot.tsx
new file mode 100644
index 000000000000..aa8d303f02ed
--- /dev/null
+++ b/packages/bot-web-ui/src/components/contract-card-loading/contract-card-running-bot.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { localize } from '@deriv/translations';
+import { Icon, Text } from '@deriv/components';
+
+export const message_running_bot = localize('Your bot is running and waiting for a signal to buy a contract.');
+
+const ContractCardRunningBot = () => (
+ <>
+
+
+ {message_running_bot}
+
+ >
+);
+
+export default ContractCardRunningBot;
diff --git a/packages/bot-web-ui/src/components/load-modal/load-modal.tsx b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx
index 5bfbfcfd86e3..0db59d90467b 100644
--- a/packages/bot-web-ui/src/components/load-modal/load-modal.tsx
+++ b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx
@@ -4,7 +4,9 @@ import { observer, useStore } from '@deriv/stores';
import { localize } from '@deriv/translations';
import { tabs_title } from 'Constants/load-modal';
import { useDBotStore } from 'Stores/useDBotStore';
+import { rudderStackSendSwitchLoadStrategyTabEvent } from '../../analytics/rudderstack-bot-builder';
import { rudderStackSendCloseEvent } from '../../analytics/rudderstack-common-events';
+import { LOAD_MODAL_TABS } from '../../analytics/utils';
import GoogleDrive from '../../pages/dashboard/load-bot-preview/google-drive';
import Local from './local';
import LocalFooter from './local-footer';
@@ -28,6 +30,13 @@ const LoadModal = observer(() => {
const { is_desktop } = ui;
const header_text = localize('Load strategy');
+ const handleTabItemClick = (active_index: number) => {
+ setActiveTabIndex(active_index);
+ rudderStackSendSwitchLoadStrategyTabEvent({
+ load_strategy_tab: LOAD_MODAL_TABS[active_index + (!is_desktop ? 1 : 0)],
+ });
+ };
+
if (!is_desktop) {
return (
{
onClickClose={() => {
setPreviewOnPopup(false);
toggleLoadModal();
- rudderStackSendCloseEvent({ subform_source: 'bot_builder', subform_name: 'load_strategy' });
+ rudderStackSendCloseEvent({
+ subform_name: 'load_strategy',
+ load_strategy_tab: LOAD_MODAL_TABS[active_index + 1],
+ });
}}
height_offset='80px'
page_overlay
>
-
+
@@ -66,13 +78,16 @@ const LoadModal = observer(() => {
is_open={is_load_modal_open}
toggleModal={() => {
toggleLoadModal();
- rudderStackSendCloseEvent({ subform_source: 'bot_builder', subform_name: 'load_strategy' });
+ rudderStackSendCloseEvent({
+ subform_name: 'load_strategy',
+ load_strategy_tab: LOAD_MODAL_TABS[active_index + (!is_desktop ? 1 : 0)],
+ });
}}
onEntered={onEntered}
elements_to_ignore={[document.querySelector('.injectionDiv')]}
>
-
+
diff --git a/packages/bot-web-ui/src/components/summary/__tests__/summary-card.spec.tsx b/packages/bot-web-ui/src/components/summary/__tests__/summary-card.spec.tsx
index b56549c414d4..5b797ac24c2c 100644
--- a/packages/bot-web-ui/src/components/summary/__tests__/summary-card.spec.tsx
+++ b/packages/bot-web-ui/src/components/summary/__tests__/summary-card.spec.tsx
@@ -18,6 +18,12 @@ jest.mock('Components/contract-card-loading', () => jest.fn(() => 'ContractCardL
const mock_contract_info: ProposalOpenContract = { account_id: 95381528 };
+const mock_props = {
+ is_contract_loading: false,
+ is_bot_running: false,
+ contract_info: mock_contract_info,
+};
+
describe('SummaryCard', () => {
let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined;
const mock_store = mockStore({});
@@ -34,7 +40,7 @@ describe('SummaryCard', () => {
});
it('should render SummaryCard component', () => {
- render( , { wrapper });
+ render( , { wrapper });
const summary_card = screen.getByTestId('dt_mock_summary_card');
@@ -42,7 +48,7 @@ describe('SummaryCard', () => {
});
it('the SummaryCard should be rendered with the inactive style when the contract is inactive, the contract is not loading, and the contract_info is null or absent', () => {
- render( , { wrapper });
+ render( , { wrapper });
const summary_card = screen.getByTestId('dt_mock_summary_card');
@@ -55,7 +61,9 @@ describe('SummaryCard', () => {
mock_DBot_store?.run_panel.setContractStage(1);
mock_DBot_store?.summary_card.onBotContractEvent({ is_sold: 1 });
- render( , { wrapper });
+ render( , {
+ wrapper,
+ });
const summary_card = screen.getByTestId('dt_mock_summary_card');
const contract_card_loader = screen.getByText('ContractCardLoader');
@@ -66,7 +74,7 @@ describe('SummaryCard', () => {
});
it('the SummaryCard should render the inner component ContractCard when the contract is not loading and the contract info exists', () => {
- render( , { wrapper });
+ render( , { wrapper });
const summary_card = screen.getByTestId('dt_mock_summary_card');
const contract_card = screen.getByText('ContractCard');
@@ -74,4 +82,16 @@ describe('SummaryCard', () => {
expect(summary_card).toBeInTheDocument();
expect(contract_card).toBeInTheDocument();
});
+
+ it('the SummaryCard should render the inner component ContractCardLoader when the bot has been running for more than 5 seconds', () => {
+ render( , {
+ wrapper,
+ });
+
+ const summary_card = screen.getByTestId('dt_mock_summary_card');
+ const contract_card_loader = screen.getByText('ContractCardLoader');
+
+ expect(summary_card).toBeInTheDocument();
+ expect(contract_card_loader).toBeInTheDocument();
+ });
});
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.scss b/packages/bot-web-ui/src/components/summary/summary-card.scss
index d31c4a64bde4..17405adec1b6 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.scss
+++ b/packages/bot-web-ui/src/components/summary/summary-card.scss
@@ -16,14 +16,12 @@
text-align: center;
line-height: 2rem;
padding: 0 3rem;
+
& .dc-text {
text-align: center;
}
}
- &--is-loading {
- background-color: inherit;
- border: 1px solid var(--general-main-1);
- }
+
&--completed {
border: 1px solid var(--general-main-1);
@@ -31,25 +29,54 @@
border: 1px solid var(--border-disabled);
}
}
+
+ &--delayed-loading {
+ align-items: center;
+ }
+
.dc-contract-card {
padding: 1.6rem 1.6rem 0.8rem;
+
+ &-message {
+ margin: 0 3rem;
+ }
}
+
.dc-contract-card__grid-underlying-trade {
grid-template-columns: 1.5fr 1fr;
}
+
.dc-contract-card-items-wrapper {
grid-template-columns: 1.5fr 1fr;
margin-top: 0.8rem;
}
+
&--mobile {
.dc-contract-card {
padding: 1.5rem 3.4rem;
}
+
.dc-contract-card__grid-underlying-trade--mobile {
grid-template-columns: 1fr 1.25fr;
}
+
.dc-contract-card-items-wrapper {
grid-template-columns: 1fr 1fr;
}
}
+
+ @keyframes rotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ #rotate-icon {
+ margin: 0 0 2rem;
+ animation: rotate 2s linear infinite;
+ }
}
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.tsx b/packages/bot-web-ui/src/components/summary/summary-card.tsx
index 3657c90647c8..5ced9809d5a9 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.tsx
+++ b/packages/bot-web-ui/src/components/summary/summary-card.tsx
@@ -9,16 +9,22 @@ import { getContractTypeDisplay } from 'Constants/contract';
import { useDBotStore } from 'Stores/useDBotStore';
import { TSummaryCardProps } from './summary-card.types';
-const SummaryCard = observer(({ contract_info, is_contract_loading }: TSummaryCardProps) => {
+const SummaryCard = observer(({ contract_info, is_contract_loading, is_bot_running }: TSummaryCardProps) => {
const { summary_card, run_panel } = useDBotStore();
const { ui, common } = useStore();
- const { is_contract_completed, is_contract_inactive, is_multiplier, is_accumulator } = summary_card;
- const { onClickSell, is_sell_requested } = run_panel;
+ const { is_contract_completed, is_contract_inactive, is_multiplier, is_accumulator, setIsBotRunning } =
+ summary_card;
+ const { onClickSell, is_sell_requested, contract_stage } = run_panel;
const { addToast, current_focus, removeToast, setCurrentFocus } = ui;
const { server_time } = common;
const { is_desktop } = ui;
+ React.useEffect(() => {
+ const cleanup = setIsBotRunning();
+ return cleanup;
+ }, [is_contract_loading]);
+
const card_header = (
- {is_contract_loading && }
- {!is_contract_loading && contract_info && (
+ {is_contract_loading && !is_bot_running && }
+ {is_bot_running && }
+ {!is_contract_loading && contract_info && !is_bot_running && (
)}
- {!is_contract_loading && !contract_info && (
+ {!is_contract_loading && !contract_info && !is_bot_running && (
{localize('When you’re ready to trade, hit ')}
{localize('Run')}
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.types.ts b/packages/bot-web-ui/src/components/summary/summary-card.types.ts
index 8ccdfb1a83cc..7f4738d30a3a 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.types.ts
+++ b/packages/bot-web-ui/src/components/summary/summary-card.types.ts
@@ -19,4 +19,5 @@ export type TContractInfo = Omit<
export interface TSummaryCardProps {
contract_info?: ProposalOpenContract | null;
is_contract_loading: boolean;
+ is_bot_running: boolean;
}
diff --git a/packages/bot-web-ui/src/components/summary/summary.scss b/packages/bot-web-ui/src/components/summary/summary.scss
index d46c0cd5b94b..100ccd5657e2 100644
--- a/packages/bot-web-ui/src/components/summary/summary.scss
+++ b/packages/bot-web-ui/src/components/summary/summary.scss
@@ -2,8 +2,10 @@
display: flex;
align-items: center;
flex-direction: column;
- background-color: var(--general-section-2);
+ justify-content: center;
+ background: linear-gradient(180deg, #f2f3f4 0%, rgba(242, 243, 244, 0) 80%);
width: 100%;
+
@include desktop-screen {
height: inherit;
}
@@ -12,6 +14,10 @@
width: 100%;
}
+ &--delayed-loading {
+ justify-content: center;
+ }
+
&__tiles {
display: flex;
flex-wrap: wrap;
@@ -37,12 +43,14 @@
margin-bottom: 4px;
@include typeface(--small-center-bold-black, none);
}
+
&-content {
height: 18px;
margin-bottom: 4px;
@include typeface(--small-center-normal-black, none);
}
}
+
&__amount {
@include typeface(--small-center-normal-black, none);
@@ -50,6 +58,7 @@
font-weight: bold;
color: var(--text-profit-success);
}
+
&--negative {
font-weight: bold;
color: var(--text-loss-danger);
diff --git a/packages/bot-web-ui/src/components/summary/summary.tsx b/packages/bot-web-ui/src/components/summary/summary.tsx
index ed60f6464cd7..87bb66aba6f5 100644
--- a/packages/bot-web-ui/src/components/summary/summary.tsx
+++ b/packages/bot-web-ui/src/components/summary/summary.tsx
@@ -12,7 +12,7 @@ type TSummary = {
const Summary = observer(({ is_drawer_open }: TSummary) => {
const { ui } = useStore();
const { dashboard, summary_card } = useDBotStore();
- const { is_contract_loading, contract_info } = summary_card;
+ const { is_contract_loading, contract_info, is_bot_running } = summary_card;
const { active_tour } = dashboard;
const { is_desktop } = ui;
return (
@@ -26,12 +26,16 @@ const Summary = observer(({ is_drawer_open }: TSummary) => {
>
-
+
);
diff --git a/packages/bot-web-ui/src/components/trade-animation/__tests__/contract-stage-text.spec.tsx b/packages/bot-web-ui/src/components/trade-animation/__tests__/contract-stage-text.spec.tsx
index 245477c549a1..87307c428f50 100644
--- a/packages/bot-web-ui/src/components/trade-animation/__tests__/contract-stage-text.spec.tsx
+++ b/packages/bot-web-ui/src/components/trade-animation/__tests__/contract-stage-text.spec.tsx
@@ -1,23 +1,14 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { contract_stages } from 'Constants/contract-stage';
-import ContractStageText from '../contract-stage-text';
-
-const stageTextMapping = Object.freeze({
- NOT_RUNNING: 'Bot is not running',
- STARTING: 'Bot is starting',
- PURCHASE_SENT: 'Buying contract',
- PURCHASE_RECEIVED: 'Contract bought',
- IS_STOPPING: 'Bot is stopping',
- CONTRACT_CLOSED: 'Contract closed',
-});
+import ContractStageText, { text_contract_stages } from '../contract-stage-text';
describe('ContractStageText', () => {
const stages = Object.keys(contract_stages);
stages.forEach(stage => {
it(`should render = ({ contract_stage }) => {
switch (contract_stage) {
case contract_stages.STARTING:
- return ;
+ return ;
+ case contract_stages.RUNNING:
+ return ;
case contract_stages.PURCHASE_SENT:
- return ;
+ return ;
case contract_stages.PURCHASE_RECEIVED:
- return ;
+ return ;
case contract_stages.IS_STOPPING:
- return ;
+ return ;
case contract_stages.CONTRACT_CLOSED:
- return ;
+ return ;
case contract_stages.NOT_RUNNING:
default:
- return ;
+ return ;
}
};
diff --git a/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss b/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss
index 79cb5fd11a30..ada443838de9 100644
--- a/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss
+++ b/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss
@@ -173,17 +173,17 @@
height: 100%;
}
- &-3 {
+ &-4 {
animation: animate-progress 0.5s 1;
animation-fill-mode: forwards;
}
- &-4 {
+ &-5 {
width: 51%;
border-radius: 5px 0 0 5px;
}
- &-5 {
+ &-6 {
animation: animate-progress-complete 0.5s 1;
animation-fill-mode: forwards;
}
diff --git a/packages/bot-web-ui/src/components/trade-animation/trade-animation.tsx b/packages/bot-web-ui/src/components/trade-animation/trade-animation.tsx
index 615dd1393319..a78520e70e0e 100644
--- a/packages/bot-web-ui/src/components/trade-animation/trade-animation.tsx
+++ b/packages/bot-web-ui/src/components/trade-animation/trade-animation.tsx
@@ -16,8 +16,9 @@ type TTradeAnimation = {
};
const TradeAnimation = observer(({ className, should_show_overlay }: TTradeAnimation) => {
- const { run_panel, summary_card } = useDBotStore();
+ const { dashboard, run_panel, summary_card } = useDBotStore();
const { client } = useStore();
+ const { active_tab } = dashboard;
const { is_contract_completed, profit } = summary_card;
const {
contract_stage,
@@ -61,7 +62,7 @@ const TradeAnimation = observer(({ className, should_show_overlay }: TTradeAnima
progress_status += 1;
}
- for (let i = 0; i < progress_status; i++) {
+ for (let i = 0; i < progress_status - 1; i++) {
status_classes[i] = 'completed';
}
}
@@ -85,6 +86,10 @@ const TradeAnimation = observer(({ className, should_show_overlay }: TTradeAnima
};
}, [is_stop_button_visible]);
const show_overlay = should_show_overlay && is_contract_completed;
+
+ const TAB_NAMES = ['dashboard', 'bot_builder', 'charts', 'tutorials'] as const;
+ const getTabName = (index: number) => TAB_NAMES[index];
+
return (
{
const handleQuickStrategyOpen = () => {
setFormVisibility(true);
// send to rs if quick strategy is opened from bot builder (mobile)
- rudderStackSendOpenEvent({ subform_source: 'bot_builder', subform_name: 'quick_strategy' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'bot_builder',
+ subform_name: 'quick_strategy',
+ });
};
return (
diff --git a/packages/bot-web-ui/src/pages/bot-builder/toolbar/workspace-group.tsx b/packages/bot-web-ui/src/pages/bot-builder/toolbar/workspace-group.tsx
index 87640a9f2986..d66cf58211a7 100644
--- a/packages/bot-web-ui/src/pages/bot-builder/toolbar/workspace-group.tsx
+++ b/packages/bot-web-ui/src/pages/bot-builder/toolbar/workspace-group.tsx
@@ -32,7 +32,12 @@ const WorkspaceGroup = observer(() => {
action={() => {
setPreviewOnPopup(true);
toggleLoadModal();
- rudderStackSendOpenEvent({ subform_source: 'bot_builder', subform_name: 'load_strategy' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'bot_builder',
+ subform_name: 'load_strategy',
+ load_strategy_tab: 'recent',
+ });
}}
/>
{
const handleQuickStrategyOpen = () => {
setFormVisibility(true);
// send to rs if quick strategy is opened from bot builder (desktop)
- rudderStackSendOpenEvent({ subform_source: 'bot_builder', subform_name: 'quick_strategy' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'bot_builder',
+ subform_name: 'quick_strategy',
+ });
};
if (is_desktop) {
diff --git a/packages/bot-web-ui/src/pages/dashboard/cards.tsx b/packages/bot-web-ui/src/pages/dashboard/cards.tsx
index 88ba02ec04d5..9df96847e4f9 100644
--- a/packages/bot-web-ui/src/pages/dashboard/cards.tsx
+++ b/packages/bot-web-ui/src/pages/dashboard/cards.tsx
@@ -49,7 +49,12 @@ const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) =>
content: is_mobile ? localize('Local') : localize('My computer'),
method: () => {
openFileLoader();
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'my_computer' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'dashboard',
+ subform_name: 'load_strategy',
+ load_strategy_tab: 'local',
+ });
},
},
{
@@ -58,7 +63,12 @@ const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) =>
content: localize('Google Drive'),
method: () => {
openGoogleDriveDialog();
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'google_drive' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'dashboard',
+ subform_name: 'load_strategy',
+ load_strategy_tab: 'google drive',
+ });
},
},
{
@@ -67,7 +77,10 @@ const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) =>
content: localize('Bot Builder'),
method: () => {
setActiveTab(DBOT_TABS.BOT_BUILDER);
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'bot_builder' });
+ rudderStackSendDashboardClickEvent({
+ dashboard_click_name: 'bot_builder',
+ subpage_name: 'bot_builder',
+ });
},
},
{
@@ -77,9 +90,11 @@ const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) =>
method: () => {
setActiveTab(DBOT_TABS.BOT_BUILDER);
setFormVisibility(true);
- // send to rs if quick strategy is opened from dashbaord
- rudderStackSendOpenEvent({ subform_source: 'dashboard', subform_name: 'quick_strategy' });
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'quick_strategy' });
+ rudderStackSendOpenEvent({
+ subpage_name: 'bot_builder',
+ subform_source: 'dashboard',
+ subform_name: 'quick_strategy',
+ });
},
},
];
diff --git a/packages/bot-web-ui/src/pages/dashboard/load-bot-preview/recent-workspace.tsx b/packages/bot-web-ui/src/pages/dashboard/load-bot-preview/recent-workspace.tsx
index 3423a1ca0832..61f09c241873 100644
--- a/packages/bot-web-ui/src/pages/dashboard/load-bot-preview/recent-workspace.tsx
+++ b/packages/bot-web-ui/src/pages/dashboard/load-bot-preview/recent-workspace.tsx
@@ -66,13 +66,13 @@ const RecentWorkspace = observer(({ workspace, index }: TRecentWorkspace) => {
const handleOpen = async () => {
await loadFileFromRecent();
setActiveTab(DBOT_TABS.BOT_BUILDER);
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'open' });
+ rudderStackSendDashboardClickEvent({ dashboard_click_name: 'open', subpage_name: 'bot_builder' });
};
const handleSave = () => {
updateBotName(workspace?.name);
toggleSaveModal();
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'save' });
+ rudderStackSendDashboardClickEvent({ dashboard_click_name: 'save', subpage_name: 'dashboard' });
};
const viewRecentStrategy = async (type: string) => {
@@ -89,7 +89,7 @@ const RecentWorkspace = observer(({ workspace, index }: TRecentWorkspace) => {
case STRATEGY.DELETE:
onToggleDeleteDialog(true);
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'delete' });
+ rudderStackSendDashboardClickEvent({ dashboard_click_name: 'delete', subpage_name: 'dashboard' });
break;
default:
diff --git a/packages/bot-web-ui/src/pages/dashboard/user-guide.tsx b/packages/bot-web-ui/src/pages/dashboard/user-guide.tsx
index 92bb572a92ff..359a34db96fb 100644
--- a/packages/bot-web-ui/src/pages/dashboard/user-guide.tsx
+++ b/packages/bot-web-ui/src/pages/dashboard/user-guide.tsx
@@ -18,7 +18,10 @@ const UserGuide: React.FC = ({ is_mobile, handleTabChange, setActive
onClick={() => {
handleTabChange(DBOT_TABS.TUTORIAL);
setActiveTabTutorial(0);
- rudderStackSendDashboardClickEvent({ dashboard_click_name: 'user_guide' });
+ rudderStackSendDashboardClickEvent({
+ dashboard_click_name: 'user_guide',
+ subpage_name: 'tutorials',
+ });
}}
data-testid='btn-user-guide'
>
diff --git a/packages/bot-web-ui/src/pages/main/main.scss b/packages/bot-web-ui/src/pages/main/main.scss
index bf9c73d026a4..cd784efba9c5 100644
--- a/packages/bot-web-ui/src/pages/main/main.scss
+++ b/packages/bot-web-ui/src/pages/main/main.scss
@@ -99,9 +99,11 @@
svg {
margin-bottom: 2.4rem;
}
+
h1 {
margin-bottom: 0.8rem;
}
+
span {
word-break: break-word;
text-align: center;
@@ -164,8 +166,14 @@
z-index: 2;
.animation {
- &__button {
- background-color: var(--purchase-main-1);
+ &__button#db-animation {
+ &__run-button {
+ background-color: var(--purchase-main-1);
+ }
+
+ &__stop-button {
+ background-color: var(--button-primary-hover);
+ }
}
&__container {
@@ -186,6 +194,20 @@
}
}
+.animation__wrapper {
+ .animation {
+ &__button#db-animation {
+ &__run-button {
+ background-color: var(--purchase-main-1);
+ }
+
+ &__stop-button {
+ background-color: var(--button-primary-hover);
+ }
+ }
+ }
+}
+
.bot-stopped-dialog {
@include mobile-or-tablet-screen {
padding: 2rem;
@@ -259,6 +281,7 @@
width: 40rem;
}
}
+
&__toolbox {
position: absolute;
top: 0;
@@ -311,6 +334,7 @@
}
}
}
+
&__run-strategy-wrapper {
@include desktop-screen {
position: absolute;
diff --git a/packages/bot-web-ui/src/pages/server-side-bot/add-bot/form.tsx b/packages/bot-web-ui/src/pages/server-side-bot/add-bot/form.tsx
index 13042f10882e..fddc8d93c908 100644
--- a/packages/bot-web-ui/src/pages/server-side-bot/add-bot/form.tsx
+++ b/packages/bot-web-ui/src/pages/server-side-bot/add-bot/form.tsx
@@ -82,7 +82,9 @@ const QuickStrategyForm = observer(() => {
switch (field.type) {
// Generic or common fields
case 'text': {
- if (field.name === 'name') return ;
+ if (field.name === 'name') {
+ return ;
+ }
return (
void;
is_mobile?: boolean;
+ disable_delete: boolean;
};
-const BotListMenu: React.FC = ({ is_open, y_position, botAction, bot_id, is_mobile = false }) => {
+const BotListMenu: React.FC = ({
+ is_open,
+ y_position,
+ botAction,
+ bot_id,
+ is_mobile = false,
+ disable_delete,
+}) => {
if (!is_open) return null;
const el_portal = document.getElementById('ssb-bot-list-menu');
if (!el_portal) return null;
@@ -41,11 +50,17 @@ const BotListMenu: React.FC = ({ is_open, y_position, botAction, b
)}
botAction('DELETE', bot_id)}
+ className={classNames('ssb-list__menu__item', {
+ 'ssb-list__menu__item--disabled': disable_delete,
+ })}
+ onClick={() => {
+ if (!disable_delete) {
+ botAction('DELETE', bot_id);
+ }
+ }}
tabIndex={0}
onKeyDown={(e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
+ if (e.key === 'Enter' && !disable_delete) {
botAction('DELETE', bot_id);
}
}}
diff --git a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss
index f239c0c257e0..27fdb37a6e82 100644
--- a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss
+++ b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss
@@ -93,9 +93,18 @@
margin-right: 8px;
}
- &:hover {
+ &:not(&--disabled):hover {
+ cursor: pointer;
background-color: var(--state-hover);
}
+ &--disabled {
+ background-color: var(--state-hover);
+ opacity: 0.5;
+ cursor: not-allowed;
+ &:hover {
+ background-color: var(--state-hover);
+ }
+ }
}
}
}
diff --git a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx
index b96904102107..99feb58f60bf 100644
--- a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx
+++ b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx
@@ -81,8 +81,7 @@ const BotList: React.FC
= observer(({ setFormVisibility }) => {
stopBot(bot_id);
break;
case 'OPEN':
- // eslint-disable-next-line no-console
- console.log('OPEN');
+ setActiveBotId(bot_id);
closeMenu();
break;
case 'DELETE':
@@ -111,6 +110,7 @@ const BotList: React.FC = observer(({ setFormVisibility }) => {
useOnClickOutside(menu_ref, closeMenu, event => menu_open.visible && !menu_ref?.current.contains(event.target));
const has_list = !!bot_list?.length;
+ const should_disable_delete = menu_open.bot_id === active_bot.bot_id && active_bot.status === 'running';
return (
<>
@@ -123,6 +123,7 @@ const BotList: React.FC = observer(({ setFormVisibility }) => {
bot_id={menu_open.bot_id}
botAction={botAction}
is_mobile={is_mobile}
+ disable_delete={should_disable_delete}
/>
diff --git a/packages/bot-web-ui/src/stores/load-modal-store.ts b/packages/bot-web-ui/src/stores/load-modal-store.ts
index 0317e77c7c7e..a4c308e599b4 100644
--- a/packages/bot-web-ui/src/stores/load-modal-store.ts
+++ b/packages/bot-web-ui/src/stores/load-modal-store.ts
@@ -7,13 +7,12 @@ import { TStores } from '@deriv/stores/types';
import { localize } from '@deriv/translations';
import { clearInjectionDiv, tabs_title } from 'Constants/load-modal';
import { TStrategy } from 'Types';
-import { rudderStackSendSwitchLoadStrategyTabEvent } from '../analytics/rudderstack-bot-builder';
import {
rudderStackSendUploadStrategyCompletedEvent,
rudderStackSendUploadStrategyFailedEvent,
rudderStackSendUploadStrategyStartEvent,
} from '../analytics/rudderstack-common-events';
-import { getStrategyType, LOAD_MODAL_TABS } from '../analytics/utils';
+import { getStrategyType } from '../analytics/utils';
import RootStore from './root-store';
interface ILoadModalStore {
@@ -50,7 +49,7 @@ interface ILoadModalStore {
onToggleDeleteDialog: (is_delete_modal_open: boolean) => void;
onZoomInOutClick: (is_zoom_in: string) => void;
previewRecentStrategy: (workspace_id: string) => void;
- setActiveTabIndex: (index: number, is_default: boolean) => void;
+ setActiveTabIndex: (index: number) => void;
setLoadedLocalFile: (loaded_local_file: File | null) => void;
setDashboardStrategies: (strategies: Array
) => void;
setRecentStrategies: (recent_strategies: TStrategy[]) => void;
@@ -456,15 +455,8 @@ export default class LoadModalStore implements ILoadModalStore {
this.refreshStrategiesTheme();
};
- setActiveTabIndex = (index: number, is_default: boolean): void => {
+ setActiveTabIndex = (index: number): void => {
this.active_index = index;
- if (!is_default) {
- const { ui } = this.core;
- const { is_mobile } = ui;
- rudderStackSendSwitchLoadStrategyTabEvent({
- load_strategy_tab: LOAD_MODAL_TABS[index + (is_mobile ? 1 : 0)],
- });
- }
};
setLoadedLocalFile = (loaded_local_file: File | null): void => {
diff --git a/packages/bot-web-ui/src/stores/summary-card-store.ts b/packages/bot-web-ui/src/stores/summary-card-store.ts
index f35a1ea6b447..4f4a4ff4a196 100644
--- a/packages/bot-web-ui/src/stores/summary-card-store.ts
+++ b/packages/bot-web-ui/src/stores/summary-card-store.ts
@@ -47,6 +47,7 @@ export default class SummaryCardStore {
contract_id?: string | null = null;
profit?: number = 0;
indicative?: number = 0;
+ is_bot_running?: boolean = false;
constructor(root_store: RootStore, core: TStores) {
makeObservable(this, {
@@ -59,6 +60,7 @@ export default class SummaryCardStore {
contract_update_stop_loss: observable,
has_contract_update_take_profit: observable,
has_contract_update_stop_loss: observable,
+ is_bot_running: observable,
contract_update_config: observable,
contract_id: observable,
profit: observable,
@@ -74,6 +76,7 @@ export default class SummaryCardStore {
onChange: action.bound,
populateContractUpdateConfig: action.bound,
setContractUpdateConfig: action.bound,
+ setIsBotRunning: action.bound,
updateLimitOrder: action.bound,
setValidationErrorMessages: action,
validateProperty: action,
@@ -212,6 +215,26 @@ export default class SummaryCardStore {
}
}
+ /**
+ * Sets the bot's running state based on whether the contract is still loading
+ */
+ setIsBotRunning() {
+ if (!this.is_contract_loading) {
+ this.is_bot_running = false;
+ return;
+ }
+
+ const onTimeout = () => {
+ if (this.is_contract_loading) {
+ this.is_bot_running = true;
+ this.root_store.run_panel.setContractStage(contract_stages.RUNNING);
+ }
+ };
+
+ const timeout = setTimeout(onTimeout, 5000);
+ return () => clearTimeout(timeout);
+ }
+
updateLimitOrder() {
const limit_order = this.getLimitOrder();
diff --git a/packages/cashier-v2/package.json b/packages/cashier-v2/package.json
index c98247c437ab..423077d4ad24 100644
--- a/packages/cashier-v2/package.json
+++ b/packages/cashier-v2/package.json
@@ -14,7 +14,7 @@
"start": "rimraf dist && npm run test && npm run serve"
},
"dependencies": {
- "@deriv-com/ui": "1.29.9",
+ "@deriv-com/ui": "1.29.10",
"@deriv-com/utils": "^0.0.25",
"@deriv/api-v2": "^1.0.0",
"@deriv/integration": "^1.0.0",
diff --git a/packages/cashier/package.json b/packages/cashier/package.json
index 12402ee5f6fa..1f053f99647f 100644
--- a/packages/cashier/package.json
+++ b/packages/cashier/package.json
@@ -37,6 +37,7 @@
"url": "https://github.com/binary-com/deriv-app/issues"
},
"dependencies": {
+ "@deriv-com/analytics": "1.11.0",
"@deriv/api": "^1.0.0",
"@deriv/api-types": "1.0.172",
"@deriv/components": "^1.0.0",
diff --git a/packages/cashier/src/components/cashier-container/real/real.scss b/packages/cashier/src/components/cashier-container/real/real.scss
index 156134338ee5..cf7dfd990046 100644
--- a/packages/cashier/src/components/cashier-container/real/real.scss
+++ b/packages/cashier/src/components/cashier-container/real/real.scss
@@ -3,4 +3,20 @@
width: 100%;
height: 80vh;
}
+
+ &__iframe {
+ @include mobile {
+ position: absolute;
+ height: calc(100% - 3.2rem);
+ inset-inline-end: 0;
+ padding: 0 1.6rem;
+
+ + .page-container__sidebar--right {
+ position: relative;
+ top: calc(100% + 1.6rem);
+ padding-bottom: 1.6rem;
+ flex: none;
+ }
+ }
+ }
}
diff --git a/packages/cashier/src/components/cashier-container/real/real.tsx b/packages/cashier/src/components/cashier-container/real/real.tsx
index 7957b165eaa8..e1e91acb67f2 100644
--- a/packages/cashier/src/components/cashier-container/real/real.tsx
+++ b/packages/cashier/src/components/cashier-container/real/real.tsx
@@ -29,7 +29,7 @@ const Real = observer(() => {
{should_show_loader && }
{iframe_url && (
diff --git a/packages/wallets/src/components/WalletsErrorScreen/WalletsErrorScreen.tsx b/packages/wallets/src/components/WalletsErrorScreen/WalletsErrorScreen.tsx
index b0f9f15359f9..98a4685025b8 100644
--- a/packages/wallets/src/components/WalletsErrorScreen/WalletsErrorScreen.tsx
+++ b/packages/wallets/src/components/WalletsErrorScreen/WalletsErrorScreen.tsx
@@ -4,20 +4,14 @@ import { WalletsActionScreen } from '../WalletsActionScreen';
import './WalletsErrorScreen.scss';
type TProps = {
- buttonText?: string;
+ buttonText?: React.ReactNode;
buttonVariant?: ComponentProps['variant'];
- message?: string;
+ message: React.ReactNode;
onClick?: () => void;
- title?: string;
+ title: React.ReactNode;
};
-const WalletsErrorScreen: React.FC = ({
- buttonText,
- buttonVariant = 'contained',
- message = 'Sorry an error occurred. Please try accessing our cashier again.',
- onClick,
- title = 'Oops, something went wrong!',
-}) => {
+const WalletsErrorScreen: React.FC = ({ buttonText, buttonVariant = 'contained', message, onClick, title }) => {
return (
{
});
it('should show the correct title with default message', () => {
- render( );
+ render(
+
+ );
expect(screen.getByText('Oops, something went wrong!')).toBeInTheDocument();
expect(
screen.getByText('Sorry an error occurred. Please try accessing our cashier again.')
).toBeInTheDocument();
});
- it('should show the message passed as prop', () => {
- render( );
- expect(screen.getByText('Error message from props')).toBeInTheDocument();
- });
-
it('should trigger onClick callback', () => {
(useDevice as jest.Mock).mockReturnValue({ isMobile: true });
const onClickHandler = jest.fn();
- render( );
+ render(
+
+ );
screen.getByRole('button', { name: 'Try again' }).click();
expect(onClickHandler).toHaveBeenCalled();
});
diff --git a/packages/wallets/src/components/WalletsResetMT5Password/WalletSuccessResetMT5Password.tsx b/packages/wallets/src/components/WalletsResetMT5Password/WalletSuccessResetMT5Password.tsx
index 0b0960108500..b1194b7e9bd7 100644
--- a/packages/wallets/src/components/WalletsResetMT5Password/WalletSuccessResetMT5Password.tsx
+++ b/packages/wallets/src/components/WalletsResetMT5Password/WalletSuccessResetMT5Password.tsx
@@ -1,5 +1,6 @@
import React, { FC, useCallback } from 'react';
import { DerivLightIcMt5PasswordUpdatedIcon, DerivLightMt5SuccessPasswordResetIcon } from '@deriv/quill-icons';
+import { useTranslations } from '@deriv-com/translations';
import useDevice from '../../hooks/useDevice';
import { ModalStepWrapper, WalletButton } from '../Base';
import { WalletsActionScreen } from '../WalletsActionScreen';
@@ -16,27 +17,35 @@ const WalletSuccessResetMT5Password: FC = ({
title,
}) => {
const { isMobile } = useDevice();
+ const { localize } = useTranslations();
const renderButtons = useCallback(() => {
return (
- {isInvestorPassword ? 'Ok' : 'Done'}
+ {isInvestorPassword ? localize('Ok') : localize('Done')}
);
- }, [isInvestorPassword, isMobile, onClick]);
+ }, [isInvestorPassword, isMobile, localize, onClick]);
return (
= ({
)
}
renderButtons={isMobile ? undefined : renderButtons}
- title={isInvestorPassword ? 'Password saved' : 'Success'}
+ title={isInvestorPassword ? localize('Password saved') : localize('Success')}
/>
diff --git a/packages/wallets/src/components/WalletsResetMT5Password/WalletsErrorMT5InvestorPassword.tsx b/packages/wallets/src/components/WalletsResetMT5Password/WalletsErrorMT5InvestorPassword.tsx
index 3f3345a95087..2fb62ca7dfa6 100644
--- a/packages/wallets/src/components/WalletsResetMT5Password/WalletsErrorMT5InvestorPassword.tsx
+++ b/packages/wallets/src/components/WalletsResetMT5Password/WalletsErrorMT5InvestorPassword.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { DerivLightIcDxtradePasswordIcon } from '@deriv/quill-icons';
+import { useTranslations } from '@deriv-com/translations';
import useDevice from '../../hooks/useDevice';
import { ModalStepWrapper } from '../Base';
import { WalletsActionScreen } from '../WalletsActionScreen';
@@ -13,12 +14,13 @@ type TProps = {
const WalletsErrorMT5InvestorPassword: React.FC = ({ errorMessage, renderButtons, title }) => {
const { isMobile } = useDevice();
+ const { localize } = useTranslations();
return (
+ ),
+ [CFD_PLATFORMS.MT5]: (
+
+ ),
} as const;
type WalletsResetMT5PasswordProps = {
@@ -49,6 +53,7 @@ const WalletsResetMT5Password = ({
} = useTradingPlatformInvestorPasswordReset();
const { hide, show } = useModal();
+ const { localize } = useTranslations();
const [password, setPassword] = useState('');
const isMT5 = platform === CFD_PLATFORMS.MT5;
@@ -101,7 +106,7 @@ const WalletsResetMT5Password = ({
errorMessage={changeInvestorPasswordError?.error?.message}
renderButtons={() => (
- Ok
+
)}
title={title}
@@ -114,7 +119,7 @@ const WalletsResetMT5Password = ({
const renderButtons = () => (
hide()} size='lg' variant='outlined'>
- Cancel
+
- Create
+
);
@@ -134,22 +139,31 @@ const WalletsResetMT5Password = ({
renderFooter={isMobile ? renderButtons : undefined}
shouldHideFooter={!isMobile}
shouldHideHeader={!isMobile}
- title={`Manage ${title} password`}
+ title={localize('Manage {{title}} password', { localize })}
>
-
- {`Create a new ${title} password`}
-
+
+
+
{isMT5 && !isInvestorPassword && (
- You can use this password for all your {title} accounts.
+
+
+
)}
setPassword(e.target.value)}
password={password}
/>
- {modalDescription[platform]}
+ {modalDescription[platform]}
{!isMobile && renderButtons()}
diff --git a/packages/wallets/src/components/index.ts b/packages/wallets/src/components/index.ts
index b0d18c914fe2..2cf9b878ccb7 100644
--- a/packages/wallets/src/components/index.ts
+++ b/packages/wallets/src/components/index.ts
@@ -6,8 +6,9 @@ export * from './DerivAppsSection';
export * from './DesktopWalletsList';
export * from './Dropzone';
export * from './FadedAnimatedList';
-export * from './FlowField';
export * from './FlowProvider';
+export * from './FormDropdown';
+export * from './FormField';
export * from './OptionsAndMultipliersListing';
export * from './SentEmailContent';
export * from './SkeletonLoader';
diff --git a/packages/wallets/src/features/accounts/constants.tsx b/packages/wallets/src/features/accounts/constants.tsx
deleted file mode 100644
index f19cfd594dc0..000000000000
--- a/packages/wallets/src/features/accounts/constants.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import {
- DerivLightIcBlurryDocumentIcon,
- DerivLightIcCroppedDocumentIcon,
- DerivLightIcDocumentAddressMismatchIcon,
- DerivLightIcDocumentNameMismatchIcon,
- DerivLightIcEnvelopeIcon,
- DerivLightIcOldIssuedDocumentMoreThan12Icon,
-} from '@deriv/quill-icons';
-import { THooks } from '../../types';
-
-type TExampleImageConfig = {
- description: React.ReactNode;
- image: React.ComponentType
>;
-};
-
-type TStatusCodes = Exclude;
-
-export const getExampleImagesConfig = (): TExampleImageConfig[] => [
- {
- description: 'Name in document doesn’t match your Deriv profile.',
- image: DerivLightIcDocumentNameMismatchIcon,
- },
- {
- description: 'Address in document doesn’t match address you entered above.',
- image: DerivLightIcDocumentAddressMismatchIcon,
- },
- {
- description: 'Document issued more than 12-months ago.',
- image: DerivLightIcOldIssuedDocumentMoreThan12Icon,
- },
- {
- description: 'Blurry document. All information must be clear and visible.',
- image: DerivLightIcBlurryDocumentIcon,
- },
- {
- description: 'Cropped document. All information must be clear and visible.',
- image: DerivLightIcCroppedDocumentIcon,
- },
- {
- description: 'An envelope with your name and address.',
- image: DerivLightIcEnvelopeIcon,
- },
-];
-
-export const statusCodes: Record = {
- expired: 'expired',
- none: 'none',
- pending: 'pending',
- rejected: 'rejected',
- suspected: 'suspected',
- verified: 'verified',
-} as const;
-
-export const ErrorCode = {
- DuplicateUpload: 'DuplicateUpload',
-} as const;
diff --git a/packages/wallets/src/features/accounts/index.ts b/packages/wallets/src/features/accounts/index.ts
index c9de5c34d487..01628104738a 100644
--- a/packages/wallets/src/features/accounts/index.ts
+++ b/packages/wallets/src/features/accounts/index.ts
@@ -1 +1 @@
-export * from './screens';
+export * from './modules';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/DocumentService.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/DocumentService.tsx
new file mode 100644
index 000000000000..c1aac1ccfecc
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/DocumentService.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { usePOI } from '@deriv/api-v2';
+import { Loader } from '@deriv-com/ui';
+import { THooks } from '../../../../types';
+import { IDVService, Onfido } from './components';
+
+type TDocumentServiceProps = {
+ onCompletion?: VoidFunction;
+ onDocumentNotAvailable?: VoidFunction;
+};
+
+const DocumentService: React.FC = ({ onCompletion, onDocumentNotAvailable }) => {
+ const { data: poiData, isLoading } = usePOI();
+ if (!poiData || isLoading) return ;
+
+ const service = poiData.current.service as THooks.POI['current']['service'];
+
+ if (service === 'onfido') {
+ return ;
+ }
+
+ if (service === 'idv') {
+ return ;
+ }
+
+ return null;
+};
+
+export default DocumentService;
diff --git a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.scss b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.scss
similarity index 76%
rename from packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.scss
rename to packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.scss
index 0e0337c6594a..1c593b0adad9 100644
--- a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.scss
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.scss
@@ -1,22 +1,27 @@
-.wallets-idv-document-upload {
+.wallets-idv-service {
+ width: 99.6rem;
+ height: 60rem;
display: flex;
flex-direction: column;
align-items: center;
- width: 95.6rem;
+ overflow-y: scroll;
+ margin: 1.6rem 0;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
+ min-height: 0;
padding: 1.6rem;
}
&__body {
+ width: 100%;
display: flex;
flex-direction: column;
+ gap: 1.6rem;
max-width: 64.2rem;
height: max-content;
- padding: 1.6rem 0;
- @include mobile {
+ @include mobile-or-tablet-screen {
padding: 0;
}
}
@@ -45,16 +50,9 @@
display: block;
margin: 1.2rem 0 0 0.8rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
margin-top: 1rem;
}
}
}
-
- &__error {
- gap: 1.6rem;
- display: flex;
- flex-direction: column;
- padding-bottom: 1.5rem;
- }
}
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.tsx
new file mode 100644
index 000000000000..8f54e953ff11
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/IDVService.tsx
@@ -0,0 +1,181 @@
+import React, { useEffect, useState } from 'react';
+import { Formik } from 'formik';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Loader } from '@deriv-com/ui';
+import { FormDropdown, FormField, ModalStepWrapper, WalletText } from '../../../../../../components';
+import { Footer } from '../../../components';
+import {
+ TVerifyPersonalDetailsValues,
+ useVerifyPersonalDetails,
+ VerifyPersonalDetails,
+} from '../VerifyPersonalDetails';
+import { IDVServiceErrorMessage } from './components';
+import { useIDVService } from './hooks';
+import { TIDVServiceValues } from './types';
+import { getDocumentNumberValidator, getDocumentTypeValidator } from './utils';
+import './IDVService.scss';
+
+type TIDVServiceProps = {
+ onCompletion?: VoidFunction;
+ onDocumentNotAvailable?: VoidFunction;
+};
+
+const IDVService: React.FC> = ({ onCompletion, onDocumentNotAvailable }) => {
+ const { localize } = useTranslations();
+ const {
+ availableDocumentOptions,
+ displayedDocumentsList,
+ documentExamples,
+ error: errorIDVDetails,
+ initialValues: initialIDVValues,
+ isLoading: isIDVDataLoading,
+ isSubmitted: isIDVSubmitted,
+ isSubmitting: isIDVSubmitting,
+ previousSubmissionErrorStatus,
+ submit: submitIDVDetails,
+ } = useIDVService();
+ const {
+ error: errorPersonalDetails,
+ initialValues: initialPersonalDetailsValues,
+ isLoading: isPersonalDetailsDataLoading,
+ isSubmitted: isPersonalDetailsSubmitted,
+ isSubmitting: isPersonalDetailsSubmitting,
+ submit: submitPersonalDetails,
+ } = useVerifyPersonalDetails();
+ const [clientHasDocuments, setClientHasDocuments] = useState(true);
+
+ const isDataLoading = isIDVDataLoading || isPersonalDetailsDataLoading;
+ const isSubmitting = isIDVSubmitting && isPersonalDetailsSubmitting;
+ const isSubmitted = isIDVSubmitted && isPersonalDetailsSubmitted;
+
+ const errorMessage = previousSubmissionErrorStatus ?? errorIDVDetails ?? errorPersonalDetails;
+
+ const submit = (values: TIDVServiceValues & TVerifyPersonalDetailsValues) => {
+ if (clientHasDocuments) {
+ submitPersonalDetails(values);
+ submitIDVDetails(values);
+ } else if (onDocumentNotAvailable) {
+ return onDocumentNotAvailable();
+ }
+ };
+
+ useEffect(() => {
+ // If IDV submission is successful, invoke external callback onCompletion()
+ if (isSubmitted && onCompletion) {
+ onCompletion();
+ }
+ }, [isSubmitted, onCompletion]);
+
+ if (isDataLoading || (!errorMessage && isSubmitting)) return ;
+
+ return (
+
+ {({ handleSubmit, isValid, values }) => {
+ const document = availableDocumentOptions[values.documentType];
+
+ const example =
+ values.documentType && documentExamples ? documentExamples[values.documentType] : undefined;
+ const documentNumberExample = example?.exampleFormat;
+ const additionalDocumentNumberExample = example?.additionalDocumentExampleFormat;
+
+ const disableSubmission = (!isValid && clientHasDocuments) || isSubmitting || !values.documentType;
+
+ const handleSelectDocument = (selectedItem: string) => {
+ setClientHasDocuments(selectedItem !== 'none');
+ };
+
+ return (
+ }
+ title={localize('Add a real MT5 account')}
+ >
+
+
+ {!!errorMessage?.message && (
+
+ )}
+
+
+
+
+
+
+ {clientHasDocuments && (
+ <>
+
+ {document?.additional && (
+
+ )}
+
+
+
+
+
+ >
+ )}
+
+ {clientHasDocuments &&
}
+
+
+ );
+ }}
+
+ );
+};
+
+export default IDVService;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.scss b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.scss
new file mode 100644
index 000000000000..f97eafade2bc
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.scss
@@ -0,0 +1,5 @@
+.wallets-idv-service-error-message {
+ gap: 1.6rem;
+ display: flex;
+ flex-direction: column;
+}
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.tsx
new file mode 100644
index 000000000000..ba7065a9804e
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/IDVServiceErrorMessage.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
+import { InlineMessage, WalletText } from '../../../../../../../components';
+import useDevice from '../../../../../../../hooks/useDevice';
+import './IDVServiceErrorMessage.scss';
+
+const IDVServiceErrorMessage = ({ message }: { message: string }) => {
+ const { isDesktop } = useDevice();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default IDVServiceErrorMessage;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/index.ts
new file mode 100644
index 000000000000..f4f587bfcfe5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/components/index.ts
@@ -0,0 +1 @@
+export { default as IDVServiceErrorMessage } from './IDVServiceErrorMessage';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/index.ts
new file mode 100644
index 000000000000..ffd1f235fbf3
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useIDVService } from './useIDVService';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/useIDVService.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/useIDVService.ts
new file mode 100644
index 000000000000..6cc1a63f85e4
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/hooks/useIDVService.ts
@@ -0,0 +1,133 @@
+import { useMemo } from 'react';
+import { FormikValues } from 'formik';
+import { useIdentityDocumentVerificationAdd, usePOI, useResidenceList, useSettings } from '@deriv/api-v2';
+import { useTranslations } from '@deriv-com/translations';
+import { TDocumentTypeItem, TErrorMessageProps, TIDVServiceValues } from '../types';
+import { documentNumberExamples, statusCodes } from '../utils';
+
+const useIDVService = () => {
+ const { localize } = useTranslations();
+ const { data: poiStatus, isLoading: isPOIStatusLoading } = usePOI();
+ const {
+ data: residenceList,
+ isLoading: isResidenceListLoading,
+ isSuccess: isResidenceListSuccess,
+ } = useResidenceList();
+ const { data: settings, isLoading: isSettingsLoading } = useSettings();
+ const {
+ error,
+ isLoading: isSubmitting,
+ isSuccess: isIDVSubmissionSuccess,
+ submitIDVDocuments,
+ } = useIdentityDocumentVerificationAdd();
+
+ const isLoading = isPOIStatusLoading || isResidenceListLoading || isSettingsLoading;
+
+ const statusMessage: Partial> = {
+ expired: localize('Your identity document has expired.'),
+ rejected: localize('We were unable to verify the identity document with the details provided.'),
+ } as const;
+
+ const [displayedDocumentsList, availableDocumentOptions] = useMemo(() => {
+ const documents: Record = {};
+ const list: TDocumentTypeItem[] = [];
+ if (isResidenceListSuccess) {
+ const residence = residenceList.filter(residence => residence.value === settings.citizen)[0];
+ if (residence) {
+ const supportedDocuments = residence.identity?.services?.idv?.documents_supported || {};
+ Object.keys(supportedDocuments).forEach(document => {
+ const pattern = supportedDocuments[document].format;
+ const text = supportedDocuments[document].display_name
+ ? localize(supportedDocuments[document].display_name as string)
+ : '';
+ const value = document;
+ documents[document] = {
+ pattern,
+ text,
+ value,
+ };
+ if (supportedDocuments[document].additional) {
+ const additional = supportedDocuments[document].additional;
+ documents[document].additional = {
+ pattern: additional?.format,
+ text: additional?.display_name ? localize(additional?.display_name) : '',
+ value: supportedDocuments[document]?.display_name || 'additional document',
+ };
+ }
+ list.push({
+ text,
+ value: document,
+ });
+ });
+ }
+ }
+
+ list.push({
+ text: localize("I don't have any of these"),
+ value: 'none',
+ });
+ return [list, documents];
+ }, [isResidenceListSuccess, localize, residenceList, settings.citizen]);
+
+ const status = poiStatus?.current.status ?? 'none';
+
+ const previousSubmissionErrorStatus =
+ status === statusCodes.expired || status === statusCodes.rejected
+ ? { code: status, message: statusMessage[status] }
+ : null;
+
+ const submit = (values: FormikValues | TIDVServiceValues) => {
+ submitIDVDocuments({
+ document_additional: values.additionalDocumentNumber,
+ document_number: values.documentNumber,
+ document_type: values.documentType,
+ issuing_country: settings.citizen ?? '',
+ });
+ };
+
+ const initialValues = {
+ documentNumber: '',
+ documentType: '',
+ } as TIDVServiceValues;
+
+ const documentExamples = useMemo(
+ () => (!settings.citizen ? undefined : documentNumberExamples[settings.citizen]),
+ [settings.citizen]
+ );
+
+ const isSubmitted = isIDVSubmissionSuccess;
+
+ return {
+ /** Contains information of all the available IDV documents in object format */
+ availableDocumentOptions,
+
+ /** Contains information of all the available IDV documents in list format for display purpose */
+ displayedDocumentsList,
+
+ /** Contains document number examples corresponding to the clients country */
+ documentExamples,
+
+ /** Error received (if any) while submitting the documents using `identity_verification_document_add` API call */
+ error: error?.error,
+
+ /** Initial Formik values */
+ initialValues,
+
+ /** Loading status for initial render of IDV form */
+ isLoading,
+
+ /** `true` if submission of IDV details is successful */
+ isSubmitted,
+
+ /** `true` if submission is in progress using the `identity_verification_document_add` API call */
+ isSubmitting,
+
+ /** Error status shown on upon IDV resubmission (provides the reason for resubmission) */
+ previousSubmissionErrorStatus,
+
+ /** Function to initiate IDV details submission */
+ submit,
+ };
+};
+
+export default useIDVService;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/index.ts
new file mode 100644
index 000000000000..6b712846fb20
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/index.ts
@@ -0,0 +1 @@
+export { default as IDVService } from './IDVService';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/index.ts
new file mode 100644
index 000000000000..fcb073fefcd6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/types.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/types.ts
new file mode 100644
index 000000000000..f0dc4af7b30a
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/types/types.ts
@@ -0,0 +1,16 @@
+import { THooks } from '../../../../../../../types';
+
+export type TDocumentTypeItem = {
+ additional?: TDocumentTypeItem;
+ pattern?: string;
+ text: string;
+ value: string;
+};
+
+export type TIDVServiceValues = {
+ additionalDocumentNumber?: string;
+ documentNumber: string;
+ documentType: string;
+};
+
+export type TErrorMessageProps = Exclude;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/contants.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/contants.ts
new file mode 100644
index 000000000000..f93138847927
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/contants.ts
@@ -0,0 +1,168 @@
+import { THooks } from '../../../../../../../types';
+/* eslint-disable sort-keys */
+type TDocumentNumberExamples = Record<
+ string,
+ Record
+>;
+
+type TStatusCodes = Exclude;
+
+export const statusCodes: Record = {
+ expired: 'expired',
+ none: 'none',
+ pending: 'pending',
+ rejected: 'rejected',
+ suspected: 'suspected',
+ verified: 'verified',
+} as const;
+
+export const documentNumberExamples: TDocumentNumberExamples = {
+ ke: {
+ alien_card: {
+ newDisplayName: '',
+ exampleFormat: '123456',
+ },
+ national_id: {
+ newDisplayName: '',
+ exampleFormat: '12345678',
+ },
+ passport: {
+ newDisplayName: '',
+ exampleFormat: 'A12345678',
+ },
+ },
+ za: {
+ national_id: {
+ newDisplayName: 'National ID',
+ exampleFormat: '1234567890123',
+ },
+ national_id_no_photo: {
+ newDisplayName: 'National ID (No Photo)',
+ exampleFormat: '1234567890123',
+ },
+ },
+ ng: {
+ bvn: {
+ newDisplayName: 'Bank Verification Number',
+ exampleFormat: '12345678901',
+ },
+ cac: {
+ newDisplayName: 'Corporate Affairs Commission',
+ exampleFormat: '12345678',
+ },
+ drivers_license: {
+ newDisplayName: '',
+ exampleFormat: 'ABC123456789',
+ },
+ nin: {
+ newDisplayName: 'National Identity Number',
+ exampleFormat: '12345678901',
+ },
+ nin_slip: {
+ newDisplayName: 'National Identity Number Slip',
+ exampleFormat: '12345678901',
+ },
+ tin: {
+ newDisplayName: 'Taxpayer identification number',
+ exampleFormat: '12345678-1234',
+ },
+ voter_id: {
+ newDisplayName: 'Voter ID',
+ exampleFormat: '1234567890123456789',
+ },
+ },
+ gh: {
+ drivers_license: {
+ newDisplayName: '',
+ exampleFormat: 'B1234567',
+ },
+ national_id: {
+ newDisplayName: 'National ID',
+ exampleFormat: 'GHA-123456789-1',
+ },
+ passport: {
+ newDisplayName: 'Passport',
+ exampleFormat: 'G1234567',
+ },
+ ssnit: {
+ newDisplayName: 'Social Security and National Insurance Trust',
+ exampleFormat: 'C123456789012',
+ },
+ voter_id: {
+ newDisplayName: 'Voter ID',
+ exampleFormat: '01234567890',
+ },
+ },
+ br: {
+ cpf: {
+ newDisplayName: 'CPF',
+ exampleFormat: '123.456.789-12',
+ },
+ },
+ ug: {
+ national_id: {
+ newDisplayName: 'National ID',
+ exampleFormat: 'CM12345678PE1D',
+ },
+ national_id_no_photo: {
+ newDisplayName: 'National ID (No Photo)',
+ exampleFormat: 'CM12345678PE1D',
+ additionalDocumentExampleFormat: '0123456789',
+ },
+ },
+ zw: {
+ national_id: {
+ newDisplayName: 'National ID',
+ exampleFormat: '081234567F53',
+ },
+ },
+ cl: {
+ national_id: {
+ exampleFormat: '123456789',
+ },
+ },
+ ar: {
+ dni: {
+ exampleFormat: '12345678',
+ },
+ },
+ mx: {
+ curp: {
+ exampleFormat: 'ABCD123456HEFGIJ00',
+ },
+ },
+ id: {
+ nik: {
+ exampleFormat: '1234567890123456',
+ },
+ },
+ in: {
+ aadhaar: {
+ exampleFormat: '123456789012',
+ additionalDocumentExampleFormat: 'ABCDE1234F',
+ },
+ drivers_license: {
+ exampleFormat: 'AB1234567890123',
+ },
+ epic: {
+ exampleFormat: 'ABC1234567',
+ },
+ pan: {
+ exampleFormat: 'ABCDE1234F',
+ },
+ passport: {
+ exampleFormat: 'A1234567',
+ additionalDocumentExampleFormat: 'AB1234567890123',
+ },
+ },
+ pe: {
+ national_id: {
+ exampleFormat: '12345678',
+ },
+ },
+ vn: {
+ national_id: {
+ exampleFormat: '12345678901',
+ },
+ },
+};
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/idvServiceValidators.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/idvServiceValidators.ts
new file mode 100644
index 000000000000..736c94c19d76
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/idvServiceValidators.ts
@@ -0,0 +1,44 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+import type { TDocumentTypeItem } from '../types';
+
+export const getDocumentTypeValidator = (localize: TTranslations['localize']) =>
+ Yup.string().required(localize('Please select a document type.'));
+
+export const getDocumentNumberValidator = (
+ document: TDocumentTypeItem,
+ example: string,
+ localize: TTranslations['localize']
+) => {
+ return Yup.string()
+ .required(localize('Please enter your {{documentName}} number.', { documentName: document.text }))
+ .test({
+ name: 'test-document-number',
+ test: (value, context) => {
+ let pattern;
+
+ if (document.pattern) {
+ try {
+ pattern = new RegExp(document.pattern);
+ } catch (err) {
+ // Passport pattern has (?i) which is not supported in RegExp
+ // Replace the (?i) flag with the 'i' flag
+ const match = document.pattern?.match(/(\(\?i\))/);
+ if (match) {
+ const patternWithoutFlag = document.pattern.replace(/(\(\?i\))/, '');
+ pattern = new RegExp(patternWithoutFlag, 'i');
+ }
+ }
+
+ if (pattern && value && !value.match(pattern)) {
+ return context.createError({
+ message: localize('Please enter the correct format. Example: {{example}}', {
+ example,
+ }),
+ });
+ }
+ }
+ return true;
+ },
+ });
+};
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/index.ts
new file mode 100644
index 000000000000..089e75e1e378
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/IDVService/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './contants';
+export * from './idvServiceValidators';
diff --git a/packages/wallets/src/features/cfd/screens/Onfido/Onfido.scss b/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.scss
similarity index 70%
rename from packages/wallets/src/features/cfd/screens/Onfido/Onfido.scss
rename to packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.scss
index 5ca3d90b077b..d6bf16e22b18 100644
--- a/packages/wallets/src/features/cfd/screens/Onfido/Onfido.scss
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.scss
@@ -4,18 +4,30 @@
display: flex;
flex-direction: column;
align-items: center;
+ gap: 2.4rem;
padding: 1.6rem 0;
- @include desktop {
+ @include desktop-screen {
max-height: calc(var(--wallets-vh, 1vh) * 100 - 23.5rem);
overflow-y: scroll;
}
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
+ gap: 1.6rem;
padding: 1.6rem;
}
+ &__loader {
+ width: 99.6rem;
+ height: 69.8rem;
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
&__wrapper {
position: relative;
min-width: 51.2rem;
@@ -24,11 +36,11 @@
align-items: center;
transition: all 1s ease;
- @include desktop {
+ @include desktop-screen {
min-height: 58.9rem;
}
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
height: 100%;
min-width: auto;
@@ -48,7 +60,7 @@
border-radius: $BORDER_RADIUS * 2;
}
&-Theme-step {
- @include mobile {
+ @include mobile-or-tablet-screen {
height: auto;
}
}
@@ -64,7 +76,7 @@
background: var(--general-main-2, #ffffff);
opacity: 0.76;
- @include mobile {
+ @include mobile-or-tablet-screen {
min-height: 50rem;
}
@@ -85,6 +97,16 @@
animation-delay: 2.5s;
}
}
+
+ &__personal-details-placeholder {
+ width: 1rem;
+ height: 35.1rem;
+
+ animation-name: personal-details-placeholder;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in;
+ animation-duration: 500ms;
+ }
}
@keyframes animate-verified-message {
@@ -93,3 +115,10 @@
opacity: 0;
}
}
+
+@keyframes personal-details-placeholder {
+ to {
+ width: 0;
+ height: 0;
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.tsx
new file mode 100644
index 000000000000..9e67d5fe8937
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/Onfido.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect } from 'react';
+import classNames from 'classnames';
+import { Formik, FormikValues } from 'formik';
+import { useOnfido } from '@deriv/api-v2';
+import { useTranslations } from '@deriv-com/translations';
+import { Loader } from '@deriv-com/ui';
+import { InlineMessage, ModalStepWrapper } from '../../../../../../components';
+import { useVerifyPersonalDetails, VerifyPersonalDetails } from '../VerifyPersonalDetails';
+import './Onfido.scss';
+
+type TOnfidoProps = {
+ onCompletion?: VoidFunction;
+};
+
+const Onfido: React.FC = ({ onCompletion }) => {
+ const { localize } = useTranslations();
+ const { data: onfidoData, isLoading: isOnfidoLoading } = useOnfido();
+ const { hasSubmitted: isOnfidoSubmissionSuccessful, onfidoContainerId } = onfidoData;
+ const {
+ error: errorPersonalDetails,
+ initialValues: initialPersonalDetailsValues,
+ isLoading: isPersonalDetailsDataLoading,
+ isSubmitted: isPersonalDetailsSubmitted,
+ submit: submitPersonalDetails,
+ } = useVerifyPersonalDetails();
+
+ const isLoading = isPersonalDetailsDataLoading || isOnfidoLoading;
+
+ useEffect(() => {
+ if (isOnfidoSubmissionSuccessful && isPersonalDetailsSubmitted && onCompletion) {
+ onCompletion();
+ }
+ }, [isOnfidoSubmissionSuccessful, isPersonalDetailsSubmitted, onCompletion]);
+
+ const onSubmit = (values: FormikValues) => {
+ submitPersonalDetails(values);
+ };
+
+ if (isLoading) return ;
+
+ return (
+
+
+ {!isPersonalDetailsSubmitted && (
+
+ {({ handleSubmit }) => {
+ return ;
+ }}
+
+ )}
+
+
+ {!isPersonalDetailsSubmitted ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default Onfido;
diff --git a/packages/wallets/src/features/cfd/screens/Onfido/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/index.ts
similarity index 100%
rename from packages/wallets/src/features/cfd/screens/Onfido/index.ts
rename to packages/wallets/src/features/accounts/modules/DocumentService/components/Onfido/index.ts
diff --git a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.scss b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.scss
similarity index 78%
rename from packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.scss
rename to packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.scss
index 1b8d313bb5bf..75bac2b564f1 100644
--- a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.scss
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.scss
@@ -1,17 +1,17 @@
-.wallets-verify-document-details {
+.wallets-verify-personal-details {
width: 100%;
max-width: 64.2rem;
border-radius: 0.8rem;
border: 0.1rem solid var(--system-light-5-active-background, #d6dadb);
- margin-bottom: 2.4rem;
padding: 1.6rem;
+ transition: all 1s ease;
&__body {
display: flex;
flex-direction: row;
padding-top: 1.6rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
flex-direction: column-reverse;
}
}
@@ -34,7 +34,7 @@
accent-color: #ff444f;
}
- @include mobile {
+ @include mobile-or-tablet-screen {
padding-top: 1.6rem;
}
@@ -59,14 +59,14 @@
flex: 1;
gap: 1.6rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
align-items: center;
}
& .wallets-textfield {
max-width: 35.9rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
max-width: 100%;
}
}
@@ -87,21 +87,8 @@
width: 100%;
}
- @include mobile {
+ @include mobile-or-tablet-screen {
padding: 0 0 1.6rem;
}
}
-
- &__placeholder {
- min-height: 64.2rem;
- animation-name: animate-placeholder;
- animation-duration: 0.5s;
- animation-fill-mode: forwards;
- }
-}
-
-@keyframes animate-placeholder {
- to {
- min-height: 0;
- }
}
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.tsx
new file mode 100644
index 000000000000..0bf5029c0e9d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/VerifyPersonalDetails.tsx
@@ -0,0 +1,116 @@
+import React, { useEffect } from 'react';
+import classNames from 'classnames';
+import { Field, useFormikContext } from 'formik';
+import moment from 'moment';
+import { TSocketError } from '@deriv/api-v2/types';
+import { DerivLightNameDobPoiIcon } from '@deriv/quill-icons';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { DatePicker, FormField, InlineMessage, WalletText } from '../../../../../../components';
+import { VerifyPersonalDetailsErrorMessage } from './components';
+import { TVerifyPersonalDetailsValues } from './types';
+import {
+ getDateOfBirthValidator,
+ getFirstNameValidator,
+ getLastNameValidator,
+ getValidateArePersonalDetailsVerified,
+} from './utils';
+import './VerifyPersonalDetails.scss';
+
+type TVerifyPersonalDetailsProps = {
+ error?: TSocketError<'get_settings'>['error'] | TSocketError<'set_settings'>['error'];
+ onVerification?: VoidFunction;
+};
+
+const VerifyPersonalDetails: React.FC = ({ error, onVerification }) => {
+ const { localize } = useTranslations();
+ const { errors, setFieldValue, values } = useFormikContext();
+
+ const dateDisplayFormat = 'DD-MM-YYYY';
+
+ const isValid = !errors.dateOfBirth && !errors.firstName && !errors.lastName;
+
+ useEffect(() => {
+ if (values.arePersonalDetailsVerified && onVerification) {
+ onVerification();
+ }
+ }, [onVerification, values.arePersonalDetailsVerified]);
+
+ const handleTNCChecked = (event: React.ChangeEvent) => {
+ setFieldValue('arePersonalDetailsVerified', event.target.checked);
+ };
+
+ return (
+
+
+
+ ]}
+ i18n_default_text='To avoid delays, enter your <0>name0> and <0>date of birth0> exactly as it appears on your identity document.'
+ />
+
+
+
+
+ getValidateArePersonalDetailsVerified(values, localize)}
+ />
+
+
+
+
+
+
+ {error &&
}
+
+ );
+};
+
+export default VerifyPersonalDetails;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/VerifyPersonalDetailsErrorMessage.tsx b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/VerifyPersonalDetailsErrorMessage.tsx
new file mode 100644
index 000000000000..a2d374b8b98f
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/VerifyPersonalDetailsErrorMessage.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { TSocketError } from '@deriv/api-v2/types';
+import { Localize } from '@deriv-com/translations';
+import { InlineMessage, WalletText } from '../../../../../../../../components';
+import useDevice from '../../../../../../../../hooks/useDevice';
+
+type TErrorMessageProps = {
+ error: TSocketError<'get_settings'>['error']['code'] | TSocketError<'set_settings'>['error']['code'];
+};
+
+const VerifyPersonalDetailsErrorMessage: React.FC = ({ error }) => {
+ const { isDesktop } = useDevice();
+
+ const handleOnClickLink = () => window.LC_API.open_chat_window();
+
+ if (error === 'DuplicateAccount') {
+ return (
+
+
+ ,
+ ]}
+ i18n_default_text='An account with these details already exists. Please make sure the details you entered are correct as only one real account is allowed per client. If this is a mistake, contact us via <0>live chat0>.'
+ />
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default VerifyPersonalDetailsErrorMessage;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/index.ts
new file mode 100644
index 000000000000..0f37e381460d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/VerifyPersonalDetailsErrorMessage/index.ts
@@ -0,0 +1 @@
+export { default as VerifyPersonalDetailsErrorMessage } from './VerifyPersonalDetailsErrorMessage';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/index.ts
new file mode 100644
index 000000000000..dbdc294f92d7
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/components/index.ts
@@ -0,0 +1 @@
+export * from './VerifyPersonalDetailsErrorMessage';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/index.ts
new file mode 100644
index 000000000000..dad75ef04924
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useVerifyPersonalDetails } from './useVerifyPersonalDetails';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/useVerifyPersonalDetails.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/useVerifyPersonalDetails.ts
new file mode 100644
index 000000000000..fb3ac0439d5b
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/hooks/useVerifyPersonalDetails.ts
@@ -0,0 +1,58 @@
+import { useState } from 'react';
+import { FormikValues } from 'formik';
+import { useSettings } from '@deriv/api-v2';
+import { getFormattedDateString } from '../../../../../../../utils/utils';
+import type { TVerifyPersonalDetailsValues } from '../types';
+
+const useVerifyPersonalDetails = () => {
+ const { data: settings, error, isError, isLoading, isSuccess, update } = useSettings();
+ const [isSubmissionInitiated, setIsSubmissionInitiated] = useState(false);
+ const [isSubmitted, setIsSubmitted] = useState(false);
+
+ const initialValues = {
+ arePersonalDetailsVerified: false,
+ dateOfBirth: getFormattedDateString(new Date((settings.date_of_birth ?? 0) * 1000)),
+ firstName: settings.first_name,
+ lastName: settings.last_name,
+ };
+
+ const submit = (values: FormikValues | TVerifyPersonalDetailsValues) => {
+ const isDirty =
+ settings.date_of_birth !== values.dateOfBirth ||
+ settings.first_name !== values.firstName ||
+ settings.last_name !== values.lastName;
+
+ if (isDirty) {
+ update({
+ date_of_birth: values.dateOfBirth,
+ first_name: values.firstName,
+ last_name: values.lastName,
+ });
+ setIsSubmissionInitiated(true);
+ } else {
+ setIsSubmitted(true);
+ }
+ };
+
+ // hasSubmissionInitiated is used for differentiating initial call's success
+ // from submission call's success as useSetting hook does not provide such difference.
+ if (isSuccess && isSubmissionInitiated) {
+ setIsSubmitted(true);
+ setIsSubmissionInitiated(false);
+ }
+
+ if (isError) {
+ setIsSubmissionInitiated(false);
+ }
+
+ return {
+ error: error?.error,
+ initialValues,
+ isLoading,
+ isSubmitted,
+ isSubmitting: isLoading && isSubmissionInitiated,
+ submit,
+ };
+};
+
+export default useVerifyPersonalDetails;
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/index.ts
new file mode 100644
index 000000000000..a757b33f9f67
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/index.ts
@@ -0,0 +1,6 @@
+import { useVerifyPersonalDetails } from './hooks';
+import type { TVerifyPersonalDetailsValues } from './types';
+
+export { default as VerifyPersonalDetails } from './VerifyPersonalDetails';
+export type { TVerifyPersonalDetailsValues };
+export { useVerifyPersonalDetails };
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/index.ts
new file mode 100644
index 000000000000..fcb073fefcd6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/types.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/types.ts
new file mode 100644
index 000000000000..a335c22ecc2d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/types/types.ts
@@ -0,0 +1,9 @@
+import { THooks } from '../../../../../../../types';
+import { getFormattedDateString } from '../../../../../../../utils/utils';
+
+export type TVerifyPersonalDetailsValues = {
+ arePersonalDetailsVerified?: boolean;
+ dateOfBirth?: ReturnType;
+ firstName: THooks.AccountSettings['first_name'];
+ lastName: THooks.AccountSettings['last_name'];
+};
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/index.ts
new file mode 100644
index 000000000000..8c2fd4c39c58
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/index.ts
@@ -0,0 +1 @@
+export * from './verifyPersonalDetailsValidationSchema';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/verifyPersonalDetailsValidationSchema.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/verifyPersonalDetailsValidationSchema.ts
new file mode 100644
index 000000000000..03933a8a9d29
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/VerifyPersonalDetails/utils/verifyPersonalDetailsValidationSchema.ts
@@ -0,0 +1,23 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+
+export const getValidateArePersonalDetailsVerified = (value: boolean, localize: TTranslations['localize']) => {
+ if (!value) return localize('Please verify personal details to proceed.');
+};
+
+export const getDateOfBirthValidator = (localize: TTranslations['localize']) =>
+ Yup.date().required(localize('Please enter your date of birth'));
+
+export const getFirstNameValidator = (localize: TTranslations['localize']) =>
+ Yup.string()
+ .required(localize('This field is required'))
+ .matches(/^[a-zA-Z\s\-.']+$/, localize('Letters, spaces, periods, hyphens, apostrophes only.'))
+ .min(1, localize('First name must have at least 1 character.'))
+ .max(50, localize('Enter no more than 50 characters.'));
+
+export const getLastNameValidator = (localize: TTranslations['localize']) =>
+ Yup.string()
+ .required(localize('This field is required'))
+ .matches(/^[a-zA-Z\s\-.']+$/, localize('Letters, spaces, periods, hyphens, apostrophes only.'))
+ .min(1, localize('Last name must have at least 1 character.'))
+ .max(50, localize('Enter no more than 50 characters.'));
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/components/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/components/index.ts
new file mode 100644
index 000000000000..3746db846104
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/components/index.ts
@@ -0,0 +1,3 @@
+export * from './IDVService';
+export * from './Onfido';
+export * from './VerifyPersonalDetails';
diff --git a/packages/wallets/src/features/accounts/modules/DocumentService/index.ts b/packages/wallets/src/features/accounts/modules/DocumentService/index.ts
new file mode 100644
index 000000000000..fc891c12fa93
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/DocumentService/index.ts
@@ -0,0 +1 @@
+export { default as DocumentService } from './DocumentService';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/ManualService.tsx b/packages/wallets/src/features/accounts/modules/ManualService/ManualService.tsx
new file mode 100644
index 000000000000..9a5d1f9c1a71
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/ManualService.tsx
@@ -0,0 +1,73 @@
+import React, { useState } from 'react';
+import { useSettings } from '@deriv/api-v2';
+import { useTranslations } from '@deriv-com/translations';
+import { Loader } from '@deriv-com/ui';
+import { ModalStepWrapper } from '../../../../components';
+import { THooks } from '../../../../types';
+import { DocumentSelection } from './components';
+import { getManualDocumentsMapper, TManualDocumentType } from './utils';
+
+type TManualServiceProps = {
+ onCompletion?: VoidFunction;
+};
+
+type TSelectedManualDocument = keyof TManualDocumentType | undefined;
+
+type TSelectedManualDocumentProps = {
+ countryCode: THooks.AccountSettings['country_code'];
+ onCompletion: TManualServiceProps['onCompletion'];
+ resetSelectedDocument: VoidFunction;
+ selection: NonNullable;
+};
+
+const SelectedManualDocument: React.FC = ({
+ countryCode,
+ onCompletion,
+ resetSelectedDocument,
+ selection,
+}) => {
+ const { localize } = useTranslations();
+ const SelectedDocument = getManualDocumentsMapper(localize)[selection].component;
+
+ return (
+
+ );
+};
+
+const ManualService: React.FC = ({ onCompletion }) => {
+ const { data: accountSettings, isLoading: isAccountSettingsLoading } = useSettings();
+ const [selection, setSelection] = useState();
+
+ if (isAccountSettingsLoading) {
+ return ;
+ }
+
+ if (selection) {
+ return (
+ {
+ setSelection(undefined);
+ }}
+ selection={selection}
+ />
+ );
+ }
+
+ return (
+
+ {
+ setSelection(document);
+ }}
+ />
+
+ );
+};
+
+export default ManualService;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.scss
similarity index 82%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.scss
index ae56f319b33c..52fcd8bc51e6 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.scss
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.scss
@@ -4,7 +4,7 @@
flex-wrap: wrap;
width: 100%;
- @include mobile {
+ @include mobile-or-tablet-screen {
column-gap: 2.4rem;
row-gap: 0.8rem;
}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.tsx
new file mode 100644
index 000000000000..c7fc6a04172c
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/DocumentRules.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { TDocumentRule } from '../../utils';
+import { DocumentRuleTile } from './components';
+import './DocumentRules.scss';
+
+type TDocumentRulesProps = {
+ hints: TDocumentRule[];
+};
+
+/** Component which shows the hints of the rules of uploaded for documents during POI */
+const DocumentRules: React.FC = ({ hints }) => (
+
+ {hints.map(hint => (
+
+ ))}
+
+);
+
+export default DocumentRules;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.scss
similarity index 77%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.scss
index 3c15d866078e..90a57ceeaff0 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.scss
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.scss
@@ -1,4 +1,4 @@
-.wallets-document-rule-hint {
+.wallets-document-rule-tile {
display: flex;
width: 15.6rem;
padding: 1.6rem;
@@ -7,7 +7,7 @@
align-items: center;
gap: 0.8rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
padding: 1.6rem;
flex: 1 0 41%;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.tsx
new file mode 100644
index 000000000000..ce0f8cbccbce
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/DocumentRuleTile.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { WalletText } from '../../../../../../../../components';
+import { TDocumentRule } from '../../../../utils';
+import './DocumentRuleTile.scss';
+
+const DocumentRuleTile: React.FC> = ({ description, icon }) => {
+ return (
+
+ {icon}
+
+ {description}
+
+
+ );
+};
+
+export default DocumentRuleTile;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/index.ts
new file mode 100644
index 000000000000..83e99197ba1d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/DocumentRuleTile/index.ts
@@ -0,0 +1 @@
+export { default as DocumentRuleTile } from './DocumentRuleTile';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/index.ts
new file mode 100644
index 000000000000..0848cbc25d8b
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/components/index.ts
@@ -0,0 +1 @@
+export * from './DocumentRuleTile';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/index.ts
new file mode 100644
index 000000000000..b1232b10fae2
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentRules/index.ts
@@ -0,0 +1 @@
+export { default as DocumentRules } from './DocumentRules';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.scss
similarity index 73%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.scss
index 08ad1ac1eed9..860441b261d5 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.scss
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.scss
@@ -1,12 +1,12 @@
.wallets-document-selection {
display: flex;
width: 99.6rem;
- height: 50rem;
+ height: 57.4rem;
flex-direction: column;
align-items: center;
flex-shrink: 0;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
height: auto;
padding: 1.6rem;
@@ -21,8 +21,9 @@
gap: 1.9rem;
width: 67.5rem;
- @include mobile {
- width: 32.8rem;
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ max-width: 60rem;
}
}
}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.tsx
new file mode 100644
index 000000000000..67ae936e8660
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/DocumentSelection.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { useSettings } from '@deriv/api-v2';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { WalletText } from '../../../../../../components/Base';
+import { getManualDocumentsMapper } from '../../utils';
+import { DocumentSelectionCard } from './components';
+import './DocumentSelection.scss';
+
+type TProps = {
+ onSelectDocument: (document: string) => void;
+};
+
+const DocumentSelection: React.FC = ({ onSelectDocument }) => {
+ const { localize } = useTranslations();
+ const { data } = useSettings();
+
+ const documents = getManualDocumentsMapper(localize);
+
+ return (
+
+
+
+
+
+ {Object.keys(documents).map(document => {
+ const { countries, description, icon, title } = documents[document];
+ if (countries && !countries.includes(data?.country_code ?? '')) {
+ return null;
+ }
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+export default DocumentSelection;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/DocumentSelectionCard.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/DocumentSelectionCard.scss
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/DocumentSelectionCard.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/DocumentSelectionCard.scss
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/DocumentSelectionCard.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/DocumentSelectionCard.tsx
new file mode 100644
index 000000000000..ef2967374be5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/DocumentSelectionCard.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { WalletText } from '../../../../../../../../components';
+import useDevice from '../../../../../../../../hooks/useDevice';
+import RightArrow from '../../../../../../../../public/images/navigation-chevron-right.svg';
+import { TManualDocumentType } from '../../../../utils';
+import './DocumentSelectionCard.scss';
+
+type TDocumentSelectionCardProps = Omit & {
+ onClick: (document: keyof TManualDocumentType) => void;
+ value: keyof TManualDocumentType;
+};
+
+const DocumentSelectionCard: React.FC = ({
+ description,
+ icon: Icon,
+ onClick,
+ title,
+ value,
+}) => {
+ const { isDesktop } = useDevice();
+ return (
+ onClick(value)}>
+
+
+
+
+ {title}
+
+ {description}
+
+
+
+
+ );
+};
+
+export default DocumentSelectionCard;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/index.ts
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/DocumentSelectionCard/index.ts
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/index.ts
new file mode 100644
index 000000000000..eb9fb34cbaba
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/components/index.ts
@@ -0,0 +1 @@
+export * from './DocumentSelectionCard';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/index.ts
rename to packages/wallets/src/features/accounts/modules/ManualService/components/DocumentSelection/index.ts
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.scss
new file mode 100644
index 000000000000..6131c4200650
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.scss
@@ -0,0 +1,61 @@
+.wallets-driving-license-upload {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ @include desktop-screen {
+ width: 99.6rem;
+ height: 60rem;
+ }
+
+ &__wrapper {
+ width: 100%;
+ max-width: 67.2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding: 2.4rem;
+ gap: 2.4rem;
+ background: var(--light-8-primary-background, #fff);
+
+ @include mobile-or-tablet-screen {
+ max-width: 100%;
+ }
+ }
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ gap: 1.6rem;
+ }
+
+ &__input-group {
+ width: 100%;
+ min-height: 7.8rem;
+ }
+
+ &__document-upload {
+ width: 100%;
+ display: grid;
+ gap: 1.7rem;
+
+ @include mobile-or-tablet-screen {
+ gap: 1.6rem;
+ }
+ }
+
+ &__divider {
+ width: 100%;
+ }
+
+ &__input-group,
+ &__dropzone {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 2.4rem;
+
+ @include mobile-or-tablet-screen {
+ grid-template-columns: unset;
+ gap: 1.6rem;
+ }
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.tsx
new file mode 100644
index 000000000000..a4a649fd0471
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/DrivingLicenseUpload.tsx
@@ -0,0 +1,171 @@
+import React, { useState } from 'react';
+import { Formik, FormikValues } from 'formik';
+import moment from 'moment';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Divider, Loader } from '@deriv-com/ui';
+import { DatePicker, Dropzone, FormField, ModalStepWrapper, WalletText } from '../../../../../../components';
+import DrivingLicenseCardBack from '../../../../../../public/images/accounts/document-back.svg';
+import DrivingLicenseCardFront from '../../../../../../public/images/accounts/driving-license-front.svg';
+import { THooks } from '../../../../../../types';
+import { Footer } from '../../../components';
+import { getGeneralDocumentRules, TManualDocumentComponent } from '../../utils';
+import { DocumentRules } from '../DocumentRules';
+import { ManualUploadErrorMessage } from '../ManualUploadErrorMessage';
+import { SelfieUpload } from '../SelfieUpload';
+import { useDrivingLicenseUpload } from './hooks';
+import { getDrivingLicenseUploadValidator } from './utils';
+import './DrivingLicenseUpload.scss';
+
+const DrivingLicenseUpload: TManualDocumentComponent = ({ documentIssuingCountryCode, onClickBack, onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ initialValues,
+ isLoading,
+ resetUploadStatus,
+ upload: upload_,
+ } = useDrivingLicenseUpload(documentIssuingCountryCode);
+ const [showSelfieUpload, setShowSelfieUpload] = useState(false);
+ const [error, setError] = useState();
+
+ const upload = async (values: FormikValues) => {
+ try {
+ await upload_(values);
+ onCompletion?.();
+ } catch (error) {
+ setError((error as THooks.DocumentUpload).error);
+ }
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {({ dirty, errors, handleSubmit, resetForm, setFieldValue, values }) => {
+ const isDrivingLicenseFormValid =
+ dirty &&
+ !errors.drivingLicenseNumber &&
+ !errors.drivingLicenseExpiryDate &&
+ !errors.drivingLicenseCardBack &&
+ !errors.drivingLicenseCardFront;
+
+ const onErrorRetry = () => {
+ resetUploadStatus();
+ resetForm();
+ setShowSelfieUpload(false);
+ onClickBack?.();
+ };
+
+ const handleOnClickNext = () => {
+ setShowSelfieUpload(true);
+ };
+
+ if (error) {
+ return ;
+ }
+
+ if (showSelfieUpload) {
+ return (
+ {
+ setShowSelfieUpload(false);
+ }}
+ onCompletion={handleSubmit}
+ />
+ );
+ }
+
+ return (
+ (
+
+ )}
+ title={localize('Add a real MT5 account')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) =>
+ setFieldValue('drivingLicenseCardFront', file)
+ }
+ />
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) =>
+ setFieldValue('drivingLicenseCardBack', file)
+ }
+ />
+
+
+
+
+
+
+ );
+ }}
+
+ );
+};
+
+export default DrivingLicenseUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/index.ts
new file mode 100644
index 000000000000..7d7d950ba3eb
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useDrivingLicenseUpload } from './useDrivingLicenseUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/useDrivingLicenseUpload.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/useDrivingLicenseUpload.ts
new file mode 100644
index 000000000000..08789a9b7fd6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/hooks/useDrivingLicenseUpload.ts
@@ -0,0 +1,115 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { DocumentUploadStatus, useDocumentUpload } from '@deriv/api-v2';
+import { THooks } from '../../../../../../../types';
+import { TSelfieUploadValues, useSelfieUpload } from '../../SelfieUpload';
+
+type TDrivingLicenseUploadValues = TSelfieUploadValues & {
+ drivingLicenseCardBack?: File;
+ drivingLicenseCardFront?: File;
+ drivingLicenseExpiryDate: string;
+ drivingLicenseNumber: string;
+};
+
+const useDrivingLicenseUpload = (documentIssuingCountryCode: THooks.AccountSettings['country_code']) => {
+ const {
+ resetStatus: resetDrivingLicenseUploadStatus,
+ status: drivingLicenseUploadStatus,
+ upload: uploadDrivingLicense,
+ } = useDocumentUpload();
+ const {
+ initialValues: initialValuesSelfieUpload,
+ resetStatus: resetSelfieUploadStatus,
+ status: selfieUploadStatus,
+ upload: uploadSelfie,
+ } = useSelfieUpload(documentIssuingCountryCode);
+
+ const isError =
+ drivingLicenseUploadStatus === DocumentUploadStatus.ERROR || selfieUploadStatus === DocumentUploadStatus.ERROR;
+ const isLoading =
+ ((drivingLicenseUploadStatus === DocumentUploadStatus.LOADING &&
+ selfieUploadStatus === DocumentUploadStatus.IDLE) ||
+ selfieUploadStatus === DocumentUploadStatus.LOADING) &&
+ !isError;
+ const isSuccess =
+ !isError &&
+ !isLoading &&
+ drivingLicenseUploadStatus === DocumentUploadStatus.SUCCESS &&
+ selfieUploadStatus === DocumentUploadStatus.SUCCESS;
+
+ const initialValues = {
+ ...initialValuesSelfieUpload,
+ drivingLicenseExpiryDate: '',
+ drivingLicenseNumber: '',
+ } as TDrivingLicenseUploadValues;
+
+ const resetUploadStatus = () => {
+ resetDrivingLicenseUploadStatus(DocumentUploadStatus.IDLE);
+ resetSelfieUploadStatus(DocumentUploadStatus.IDLE);
+ };
+
+ const uploadFront = useCallback(
+ (values: FormikValues | TDrivingLicenseUploadValues) => {
+ return uploadDrivingLicense({
+ document_id: values.drivingLicenseNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'driving_licence',
+ expiration_date: values.drivingLicenseExpiryDate,
+ file: values.drivingLicenseCardFront,
+ page_type: 'front',
+ });
+ },
+ [documentIssuingCountryCode, uploadDrivingLicense]
+ );
+
+ const uploadBack = useCallback(
+ (values: FormikValues | TDrivingLicenseUploadValues) => {
+ return uploadDrivingLicense({
+ document_id: values.drivingLicenseNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'driving_licence',
+ expiration_date: values.drivingLicenseExpiryDate,
+ file: values.drivingLicenseCardBack,
+ page_type: 'back',
+ });
+ },
+ [documentIssuingCountryCode, uploadDrivingLicense]
+ );
+
+ const upload = useCallback(
+ async (values: FormikValues | TDrivingLicenseUploadValues) => {
+ try {
+ await uploadFront(values);
+ await uploadBack(values);
+ await uploadSelfie(values, values.drivingLicenseNumber);
+
+ return Promise.resolve();
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [uploadBack, uploadFront, uploadSelfie]
+ );
+
+ return {
+ /** initial values for the driving-license and selfie forms */
+ initialValues,
+
+ /** `true` when driving-license/selfie upload encounter error */
+ isError,
+
+ /** `true` when driving-license/selfie are uploading */
+ isLoading,
+
+ /** `true` when driving-license/selfie are uploaded successfully */
+ isSuccess,
+
+ /** Reset upload statuses for driving-license and selfie */
+ resetUploadStatus,
+
+ /** upload driving-license and selfie files synchronously */
+ upload,
+ };
+};
+
+export default useDrivingLicenseUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/index.ts
new file mode 100644
index 000000000000..680188804e54
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/index.ts
@@ -0,0 +1 @@
+export { default as DrivingLicenseUpload } from './DrivingLicenseUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/drivingLicenseUploadValidator.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/drivingLicenseUploadValidator.ts
new file mode 100644
index 000000000000..c425fe864094
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/drivingLicenseUploadValidator.ts
@@ -0,0 +1,13 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+import { selfieUploadValidator } from '../../SelfieUpload/utils';
+import { fileValidator, getDocumentNumberValidator, getExpiryDateValidator } from '../../utils';
+
+export const getDrivingLicenseUploadValidator = (localize: TTranslations['localize']) =>
+ Yup.object().shape({
+ drivingLicenseCardBack: fileValidator,
+ drivingLicenseCardFront: fileValidator,
+ drivingLicenseExpiryDate: getExpiryDateValidator(localize),
+ drivingLicenseNumber: getDocumentNumberValidator('Driving license', localize),
+ selfieFile: selfieUploadValidator,
+ });
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/index.ts
new file mode 100644
index 000000000000..34ffc5f931ed
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/DrivingLicenseUpload/utils/index.ts
@@ -0,0 +1 @@
+export * from './drivingLicenseUploadValidator';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.scss
new file mode 100644
index 000000000000..5ef438ef14db
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.scss
@@ -0,0 +1,60 @@
+.wallets-identity-card-upload {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ @include desktop-screen {
+ width: 99.6rem;
+ height: 60rem;
+ }
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ gap: 1.6rem;
+ }
+
+ &__wrapper {
+ width: 100%;
+ max-width: 67.2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding: 2.4rem;
+ gap: 2.4rem;
+
+ @include mobile-or-tablet-screen {
+ max-width: 100%;
+ }
+ }
+
+ &__input-group {
+ width: 100%;
+ min-height: 7.8rem;
+ }
+
+ &__document-upload {
+ width: 100%;
+ display: grid;
+ gap: 1.7rem;
+
+ @include mobile-or-tablet-screen {
+ gap: 1.6rem;
+ }
+ }
+
+ &__divider {
+ width: 100%;
+ }
+
+ &__input-group,
+ &__dropzone {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 2.4rem;
+
+ @include mobile-or-tablet-screen {
+ grid-template-columns: unset;
+ gap: 1.6rem;
+ }
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.tsx
new file mode 100644
index 000000000000..db4f460cf093
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/IdentityCardUpload.tsx
@@ -0,0 +1,169 @@
+import React, { useState } from 'react';
+import { Formik, FormikValues } from 'formik';
+import moment from 'moment';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Divider, Loader } from '@deriv-com/ui';
+import { DatePicker, Dropzone, FormField, ModalStepWrapper, WalletText } from '../../../../../../components';
+import IdentityCardBack from '../../../../../../public/images/accounts/document-back.svg';
+import IdentityCardFront from '../../../../../../public/images/accounts/identity-card-front.svg';
+import { THooks } from '../../../../../../types';
+import { Footer } from '../../../components';
+import { getGeneralDocumentRules, TManualDocumentComponent } from '../../utils';
+import { DocumentRules } from '../DocumentRules';
+import { ManualUploadErrorMessage } from '../ManualUploadErrorMessage';
+import { SelfieUpload } from '../SelfieUpload';
+import { useIdentityCardUpload } from './hooks';
+import { getIdentityCardUploadValidator } from './utils';
+import './IdentityCardUpload.scss';
+
+const IdentityCardUpload: TManualDocumentComponent = ({ documentIssuingCountryCode, onClickBack, onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ initialValues,
+ isLoading,
+ resetUploadStatus,
+ upload: upload_,
+ } = useIdentityCardUpload(documentIssuingCountryCode);
+ const [showSelfieUpload, setShowSelfieUpload] = useState(false);
+ const [error, setError] = useState();
+
+ const upload = async (values: FormikValues) => {
+ try {
+ await upload_(values);
+ onCompletion?.();
+ } catch (error) {
+ setError((error as THooks.DocumentUpload).error);
+ }
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {({ dirty, errors, handleSubmit, resetForm, setFieldValue, values }) => {
+ const isIdentityCardFormValid =
+ dirty &&
+ !errors.identityCardNumber &&
+ !errors.identityCardExpiryDate &&
+ !errors.identityCardBack &&
+ !errors.identityCardFront;
+
+ const handleOnClickNext = () => {
+ if (isIdentityCardFormValid) {
+ setShowSelfieUpload(true);
+ }
+ };
+
+ const onErrorRetry = () => {
+ resetUploadStatus();
+ resetForm();
+ setShowSelfieUpload(false);
+ onClickBack?.();
+ };
+
+ if (error) {
+ return ;
+ }
+
+ if (showSelfieUpload) {
+ return (
+ {
+ setShowSelfieUpload(false);
+ }}
+ onCompletion={handleSubmit}
+ />
+ );
+ }
+
+ return (
+ (
+
+ )}
+ title={localize('Add a real MT5 account')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) => setFieldValue('identityCardFront', file)}
+ />
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) => setFieldValue('identityCardBack', file)}
+ />
+
+
+
+
+
+
+ );
+ }}
+
+ );
+};
+
+export default IdentityCardUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/index.ts
new file mode 100644
index 000000000000..15bbd4768633
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useIdentityCardUpload } from './useIdentityCardUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/useIdentityCardUpload.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/useIdentityCardUpload.ts
new file mode 100644
index 000000000000..13edeffc5215
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/hooks/useIdentityCardUpload.ts
@@ -0,0 +1,115 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { DocumentUploadStatus, useDocumentUpload } from '@deriv/api-v2';
+import { THooks } from '../../../../../../../types';
+import { TSelfieUploadValues, useSelfieUpload } from '../../SelfieUpload';
+
+type TIdentityCardUploadValues = TSelfieUploadValues & {
+ identityCardBack?: File;
+ identityCardExpiryDate: string;
+ identityCardFront?: File;
+ identityCardNumber: string;
+};
+
+const useIdentityCardUpload = (documentIssuingCountryCode: THooks.AccountSettings['country_code']) => {
+ const {
+ resetStatus: resetIdentityCardUploadStatus,
+ status: identityCardUploadStatus,
+ upload: uploadIdentityCard,
+ } = useDocumentUpload();
+ const {
+ initialValues: initialValuesSelfieUpload,
+ resetStatus: resetSelfieUploadStatus,
+ status: selfieUploadStatus,
+ upload: uploadSelfie,
+ } = useSelfieUpload(documentIssuingCountryCode);
+
+ const isError =
+ identityCardUploadStatus === DocumentUploadStatus.ERROR || selfieUploadStatus === DocumentUploadStatus.ERROR;
+ const isLoading =
+ ((identityCardUploadStatus === DocumentUploadStatus.LOADING &&
+ selfieUploadStatus === DocumentUploadStatus.IDLE) ||
+ selfieUploadStatus === DocumentUploadStatus.LOADING) &&
+ !isError;
+ const isSuccess =
+ !isError &&
+ !isLoading &&
+ identityCardUploadStatus === DocumentUploadStatus.SUCCESS &&
+ selfieUploadStatus === DocumentUploadStatus.SUCCESS;
+
+ const initialValues = {
+ ...initialValuesSelfieUpload,
+ identityCardExpiryDate: '',
+ identityCardNumber: '',
+ } as TIdentityCardUploadValues;
+
+ const resetUploadStatus = () => {
+ resetIdentityCardUploadStatus(DocumentUploadStatus.IDLE);
+ resetSelfieUploadStatus(DocumentUploadStatus.IDLE);
+ };
+
+ const uploadFront = useCallback(
+ (values: FormikValues) => {
+ return uploadIdentityCard({
+ document_id: values.identityCardNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: values.identityCardExpiryDate,
+ file: values.identityCardFront,
+ page_type: 'front',
+ });
+ },
+ [uploadIdentityCard, documentIssuingCountryCode]
+ );
+
+ const uploadBack = useCallback(
+ (values: FormikValues) => {
+ return uploadIdentityCard({
+ document_id: values.identityCardNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: values.identityCardExpiryDate,
+ file: values.identityCardBack,
+ page_type: 'back',
+ });
+ },
+ [uploadIdentityCard, documentIssuingCountryCode]
+ );
+
+ const upload = useCallback(
+ async (values: FormikValues | TIdentityCardUploadValues) => {
+ try {
+ await uploadFront(values);
+ await uploadBack(values);
+ await uploadSelfie(values, values.identityCardNumber);
+
+ return Promise.resolve();
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [uploadBack, uploadFront, uploadSelfie]
+ );
+
+ return {
+ /** initial values for the identity-card and selfie forms */
+ initialValues,
+
+ /** `true` when identity-card/selfie upload encounter error */
+ isError,
+
+ /** `true` when identity-card/selfie are uploading */
+ isLoading,
+
+ /** `true` when identity-card/selfie are uploaded successfully */
+ isSuccess,
+
+ /** Reset upload statuses for identity-card and selfie */
+ resetUploadStatus,
+
+ /** upload identity-card and selfie files synchronously */
+ upload,
+ };
+};
+
+export default useIdentityCardUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/index.ts
new file mode 100644
index 000000000000..57f5f8fc03d9
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/index.ts
@@ -0,0 +1 @@
+export { default as IdentityCardUpload } from './IdentityCardUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/identityCardUploadValidator.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/identityCardUploadValidator.ts
new file mode 100644
index 000000000000..05a0a0d78566
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/identityCardUploadValidator.ts
@@ -0,0 +1,13 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+import { selfieUploadValidator } from '../../SelfieUpload/utils';
+import { fileValidator, getDocumentNumberValidator, getExpiryDateValidator } from '../../utils';
+
+export const getIdentityCardUploadValidator = (localize: TTranslations['localize']) =>
+ Yup.object().shape({
+ identityCardBack: fileValidator,
+ identityCardExpiryDate: getExpiryDateValidator(localize),
+ identityCardFront: fileValidator,
+ identityCardNumber: getDocumentNumberValidator('Identity card', localize),
+ selfieFile: selfieUploadValidator,
+ });
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/index.ts
new file mode 100644
index 000000000000..6efdd47d795f
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/IdentityCardUpload/utils/index.ts
@@ -0,0 +1 @@
+export * from './identityCardUploadValidator';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.scss
new file mode 100644
index 000000000000..06c0a2c5fc3f
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.scss
@@ -0,0 +1,16 @@
+.wallets-manual-upload-error-message {
+ width: 100%;
+ min-width: 99.6rem;
+ height: 100%;
+ min-height: calc(var(--wallets-vh, 1vh) * 100 - 30rem);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ height: 100%;
+ padding: auto 1.6rem;
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.tsx
new file mode 100644
index 000000000000..f09102e90556
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/ManualUploadErrorMessage.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { DerivLightDeclinedPoiIcon } from '@deriv/quill-icons';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { ActionScreen, Button } from '@deriv-com/ui';
+import { ModalStepWrapper } from '../../../../../../components';
+import './ManualUploadErrorMessage.scss';
+
+type TManualUploadErrorMessageProps = {
+ errorCode: string;
+ onRetry: VoidFunction;
+};
+
+const ManualUploadErrorMessage: React.FC = ({ errorCode, onRetry }) => {
+ const { localize } = useTranslations();
+
+ const errorCodeToDescriptionMapper: Record = {
+ DuplicateUpload: localize("It seems you've submitted this document before. Upload a new document."),
+ } as const;
+
+ return (
+
+
+
+
+
+ }
+ description={errorCodeToDescriptionMapper[errorCode]}
+ icon={ }
+ title={localize('Proof of identity documents upload failed')}
+ />
+
+
+ );
+};
+
+export default ManualUploadErrorMessage;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/index.ts
new file mode 100644
index 000000000000..5ab5a836ace5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/ManualUploadErrorMessage/index.ts
@@ -0,0 +1 @@
+export { default as ManualUploadErrorMessage } from './ManualUploadErrorMessage';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.scss
similarity index 58%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.scss
index 1a284f94884c..c561bfb0c399 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.scss
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.scss
@@ -1,13 +1,28 @@
-.wallets-nimc-slip-document-upload {
+.wallets-nimc-slip-upload {
display: flex;
flex-direction: column;
- align-items: flex-start;
- gap: 2.4rem;
- padding: 2.4rem;
- width: fit-content;
- background: var(--light-8-primary-background, #fff);
+ align-items: center;
- @include mobile {
+ @include desktop-screen {
+ width: 99.6rem;
+ height: 60rem;
+ }
+
+ &__wrapper {
+ max-width: 440rem;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 2.4rem;
+ padding: 2.4rem;
+ background: var(--light-8-primary-background, #fff);
+
+ @include mobile-or-tablet-screen {
+ max-width: 100%;
+ }
+ }
+
+ @include mobile-or-tablet-screen {
width: 100%;
}
@@ -18,7 +33,7 @@
align-items: flex-start;
gap: 1.7rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
gap: 1.6rem;
}
}
@@ -29,7 +44,7 @@
align-items: flex-start;
gap: 2.4rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
flex-direction: column;
}
@@ -53,13 +68,13 @@
height: 25rem;
width: 32.4rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
}
& .wallets-dropzone {
- @include mobile {
+ @include mobile-or-tablet-screen {
padding: 2rem;
}
}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.tsx
new file mode 100644
index 000000000000..5a044554532f
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/NIMCSlipUpload.tsx
@@ -0,0 +1,151 @@
+import React, { useState } from 'react';
+import { Formik, FormikValues } from 'formik';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Divider, Loader } from '@deriv-com/ui';
+import { Dropzone, FormField, ModalStepWrapper, WalletText } from '../../../../../../components';
+import NIMCSlipFront from '../../../../../../public/images/accounts/nimc-slip-front.svg';
+import ProofOfAgeIcon from '../../../../../../public/images/accounts/proof-of-age.svg';
+import { THooks } from '../../../../../../types';
+import { Footer } from '../../../components';
+import { getNIMCDocumentRules, TManualDocumentComponent } from '../../utils';
+import { DocumentRules } from '../DocumentRules';
+import { ManualUploadErrorMessage } from '../ManualUploadErrorMessage';
+import { SelfieUpload } from '../SelfieUpload';
+import { useNIMCSlipUpload } from './hooks';
+import { getNimcSlipUploadValidator } from './utils';
+import './NIMCSlipUpload.scss';
+
+const NIMCSlipUpload: TManualDocumentComponent = ({ documentIssuingCountryCode, onClickBack, onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ initialValues,
+ isLoading,
+ resetUploadStatus,
+ upload: upload_,
+ } = useNIMCSlipUpload(documentIssuingCountryCode);
+ const [showSelfieUpload, setShowSelfieUpload] = useState(false);
+ const [error, setError] = useState();
+
+ const upload = async (values: FormikValues) => {
+ try {
+ await upload_(values);
+ onCompletion?.();
+ } catch (error) {
+ setError((error as THooks.DocumentUpload).error);
+ }
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {({ dirty, errors, handleSubmit, resetForm, setFieldValue, values }) => {
+ const isNIMCFormValid = dirty && !errors.nimcNumber && !errors.nimcCardFront && !errors.nimcCardBack;
+
+ const handleOnClickNext = () => {
+ setShowSelfieUpload(true);
+ };
+
+ const onErrorRetry = () => {
+ resetUploadStatus();
+ resetForm();
+ setShowSelfieUpload(false);
+ onClickBack?.();
+ };
+
+ if (error) {
+ return ;
+ }
+
+ if (showSelfieUpload) {
+ return (
+ {
+ setShowSelfieUpload(false);
+ }}
+ onCompletion={handleSubmit}
+ />
+ );
+ }
+ return (
+ (
+
+ )}
+ title={localize('Add a real MT5 account')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) => setFieldValue('nimcCardFront', file)}
+ />
+
+
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={(file?: File) => setFieldValue('nimcCardBack', file)}
+ />
+
+
+
+
+
+
+
+ );
+ }}
+
+ );
+};
+
+export default NIMCSlipUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/index.ts
new file mode 100644
index 000000000000..a5f292ab66e3
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useNIMCSlipUpload } from './useNIMCSlipUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/useNIMCSlipUpload.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/useNIMCSlipUpload.ts
new file mode 100644
index 000000000000..7410724cfbba
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/hooks/useNIMCSlipUpload.ts
@@ -0,0 +1,108 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { DocumentUploadStatus, useDocumentUpload } from '@deriv/api-v2';
+import { THooks } from '../../../../../../../types';
+import { TSelfieUploadValues, useSelfieUpload } from '../../SelfieUpload';
+
+type TNIMCSlipUploadValues = TSelfieUploadValues & {
+ nimcCardBack?: File;
+ nimcCardFront?: File;
+ nimcNumber: string;
+};
+
+const useNIMCSlipUpload = (documentIssuingCountryCode: THooks.AccountSettings['country_code']) => {
+ const { resetStatus: resetNIMCUploadStatus, status: nimcUploadStatus, upload: uploadNIMC } = useDocumentUpload();
+ const {
+ initialValues: initialValuesSelfieUpload,
+ resetStatus: resetSelfieUploadStatus,
+ status: selfieUploadStatus,
+ upload: uploadSelfie,
+ } = useSelfieUpload(documentIssuingCountryCode);
+
+ const isError =
+ nimcUploadStatus === DocumentUploadStatus.ERROR || selfieUploadStatus === DocumentUploadStatus.ERROR;
+ const isLoading =
+ ((nimcUploadStatus === DocumentUploadStatus.LOADING && selfieUploadStatus === DocumentUploadStatus.IDLE) ||
+ selfieUploadStatus === DocumentUploadStatus.LOADING) &&
+ !isError;
+ const isSuccess =
+ !isError &&
+ !isLoading &&
+ nimcUploadStatus === DocumentUploadStatus.SUCCESS &&
+ selfieUploadStatus === DocumentUploadStatus.SUCCESS;
+
+ const initialValues = {
+ ...initialValuesSelfieUpload,
+ nimcNumber: '',
+ } as TNIMCSlipUploadValues;
+
+ const resetUploadStatus = () => {
+ resetNIMCUploadStatus(DocumentUploadStatus.IDLE);
+ resetSelfieUploadStatus(DocumentUploadStatus.IDLE);
+ };
+
+ const uploadFront = useCallback(
+ (values: FormikValues) => {
+ return uploadNIMC({
+ document_id: values.identityCardNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: values.identityCardExpiryDate,
+ file: values.identityCardFront,
+ page_type: 'front',
+ });
+ },
+ [documentIssuingCountryCode, uploadNIMC]
+ );
+
+ const uploadBack = useCallback(
+ (values: FormikValues) => {
+ return uploadNIMC({
+ document_id: values.identityCardNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: values.identityCardExpiryDate,
+ file: values.identityCardBack,
+ page_type: 'back',
+ });
+ },
+ [documentIssuingCountryCode, uploadNIMC]
+ );
+
+ const upload = useCallback(
+ async (values: FormikValues | TNIMCSlipUploadValues) => {
+ try {
+ await uploadFront(values);
+ await uploadBack(values);
+ await uploadSelfie(values, values.nimcNumber);
+
+ return Promise.resolve();
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [uploadBack, uploadFront, uploadSelfie]
+ );
+
+ return {
+ /** initial values for the nimc-slip and selfie forms */
+ initialValues,
+
+ /** `true` when nimc-slip/selfie upload encounter error */
+ isError,
+
+ /** `true` when nimc-slip/selfie are uploading */
+ isLoading,
+
+ /** `true` when nimc-slip/selfie are uploaded successfully */
+ isSuccess,
+
+ /** Reset upload statuses for nimc-slip and selfie */
+ resetUploadStatus,
+
+ /** upload nimc-slip and selfie files synchronously */
+ upload,
+ };
+};
+
+export default useNIMCSlipUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/index.ts
new file mode 100644
index 000000000000..adbe9f904e59
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/index.ts
@@ -0,0 +1 @@
+export { default as NIMCSlipUpload } from './NIMCSlipUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/index.ts
new file mode 100644
index 000000000000..a59de4237eff
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/index.ts
@@ -0,0 +1 @@
+export * from './nimcSlipUploadValidator';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/nimcSlipUploadValidator.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/nimcSlipUploadValidator.ts
new file mode 100644
index 000000000000..6089f9376c03
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/NIMCSlipUpload/utils/nimcSlipUploadValidator.ts
@@ -0,0 +1,15 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+import { selfieUploadValidator } from '../../SelfieUpload/utils';
+import { fileValidator } from '../../utils';
+
+export const getNimcSlipUploadValidator = (localize: TTranslations['localize']) =>
+ Yup.object().shape({
+ nimcCardBack: fileValidator,
+ nimcCardFront: fileValidator,
+ nimcNumber: Yup.string().matches(
+ /^\d{11}$/,
+ localize('Please enter your document number. Example: 12345678901')
+ ),
+ selfieFile: selfieUploadValidator,
+ });
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.scss
new file mode 100644
index 000000000000..b6c2d40c16c5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.scss
@@ -0,0 +1,76 @@
+.wallets-passport-upload {
+ width: 99.6rem;
+ height: 60rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ height: 100%;
+ }
+
+ &__wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding: 2.4rem;
+ gap: 2.4rem;
+
+ @include desktop-screen {
+ width: 67.2rem;
+ height: 100%;
+ }
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ gap: 1.6rem;
+ }
+ }
+
+ &__document-upload {
+ width: 100%;
+ display: grid;
+ gap: 1.7rem;
+
+ @include mobile-or-tablet-screen {
+ gap: 1.6rem;
+ }
+ }
+
+ &__input-group {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 2.4rem;
+ width: 100%;
+ min-height: 7.8rem;
+
+ @include mobile-or-tablet-screen {
+ grid-template-columns: unset;
+ gap: 1.6rem;
+ }
+ }
+
+ &__divider {
+ width: 100%;
+ }
+
+ & .wallets-dropzone__container {
+ min-height: 18.6rem;
+
+ @include mobile-or-tablet-screen {
+ min-height: 25rem;
+ }
+ }
+
+ &__footer {
+ width: 100%;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.8rem;
+
+ @include mobile-or-tablet-screen {
+ justify-content: center;
+ }
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.tsx
new file mode 100644
index 000000000000..a643da63a1fc
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/PassportUpload.tsx
@@ -0,0 +1,143 @@
+import React, { useState } from 'react';
+import { Formik, FormikValues } from 'formik';
+import moment from 'moment';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Divider, Loader } from '@deriv-com/ui';
+import { DatePicker, Dropzone, FormField, ModalStepWrapper, WalletText } from '../../../../../../components';
+import PassportPlaceholder from '../../../../../../public/images/accounts/passport-placeholder.svg';
+import { THooks } from '../../../../../../types';
+import { Footer } from '../../../components';
+import { getGeneralDocumentRules, TManualDocumentComponent } from '../../utils';
+import { DocumentRules } from '../DocumentRules';
+import { ManualUploadErrorMessage } from '../ManualUploadErrorMessage';
+import { SelfieUpload } from '../SelfieUpload';
+import { usePassportUpload } from './hooks';
+import { getPassportUploadValidator } from './utils';
+import './PassportUpload.scss';
+
+const PassportUpload: TManualDocumentComponent = ({ documentIssuingCountryCode, onClickBack, onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ initialValues,
+ isLoading,
+ resetUploadStatus,
+ upload: upload_,
+ } = usePassportUpload(documentIssuingCountryCode);
+ const [showSelfieUpload, setShowSelfieUpload] = useState(false);
+ const [error, setError] = useState();
+
+ const upload = async (values: FormikValues) => {
+ try {
+ await upload_(values);
+ onCompletion?.();
+ } catch (error) {
+ setError((error as THooks.DocumentUpload).error);
+ }
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {({ dirty, errors, handleSubmit, resetForm, setFieldValue, values }) => {
+ const handleFileChange = (file?: File) => {
+ setFieldValue('passportFile', file);
+ };
+ const isPassportFormValid =
+ dirty && !errors.passportExpiryDate && !errors.passportFile && !errors.passportNumber;
+
+ const handleOnClickNext = () => {
+ if (isPassportFormValid) {
+ setShowSelfieUpload(true);
+ }
+ };
+
+ const onErrorRetry = () => {
+ resetUploadStatus();
+ resetForm();
+ setShowSelfieUpload(false);
+ onClickBack?.();
+ };
+
+ if (error) {
+ return ;
+ }
+
+ if (showSelfieUpload) {
+ return (
+ {
+ setShowSelfieUpload(false);
+ }}
+ onCompletion={handleSubmit}
+ />
+ );
+ }
+
+ return (
+ (
+
+ )}
+ title={localize('Add a real MT5 account')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ maxSize={8388608}
+ noClick
+ onFileChange={handleFileChange}
+ />
+
+
+
+
+
+ );
+ }}
+
+ );
+};
+
+export default PassportUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/index.ts
new file mode 100644
index 000000000000..c2524dbe3dd5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as usePassportUpload } from './usePassportUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/usePassportUpload.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/usePassportUpload.ts
new file mode 100644
index 000000000000..379da61e7b96
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/hooks/usePassportUpload.ts
@@ -0,0 +1,90 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { DocumentUploadStatus, useDocumentUpload } from '@deriv/api-v2';
+import { THooks } from '../../../../../../../types';
+import { TSelfieUploadValues, useSelfieUpload } from '../../SelfieUpload';
+
+type TPassportUploadValues = TSelfieUploadValues & {
+ passportExpiryDate: string | null;
+ passportFile?: File;
+ passportNumber: string;
+};
+
+const usePassportUpload = (documentIssuingCountryCode: THooks.AccountSettings['country_code']) => {
+ const {
+ resetStatus: resetPassportUploadStatus,
+ status: passportUploadStatus,
+ upload: uploadPassport,
+ } = useDocumentUpload();
+ const {
+ initialValues: initialValuesSelfieUpload,
+ resetStatus: resetSelfieUploadStatus,
+ status: selfieUploadStatus,
+ upload: uploadSelfie,
+ } = useSelfieUpload(documentIssuingCountryCode);
+
+ const isError =
+ passportUploadStatus === DocumentUploadStatus.ERROR || selfieUploadStatus === DocumentUploadStatus.ERROR;
+ const isLoading =
+ ((passportUploadStatus === DocumentUploadStatus.LOADING && selfieUploadStatus === DocumentUploadStatus.IDLE) ||
+ selfieUploadStatus === DocumentUploadStatus.LOADING) &&
+ !isError;
+ const isSuccess =
+ !isError &&
+ !isLoading &&
+ passportUploadStatus === DocumentUploadStatus.SUCCESS &&
+ selfieUploadStatus === DocumentUploadStatus.SUCCESS;
+
+ const initialValues = {
+ ...initialValuesSelfieUpload,
+ passportExpiryDate: null,
+ passportNumber: '',
+ } as TPassportUploadValues;
+
+ const resetUploadStatus = () => {
+ resetPassportUploadStatus(DocumentUploadStatus.IDLE);
+ resetSelfieUploadStatus(DocumentUploadStatus.IDLE);
+ };
+
+ const upload = useCallback(
+ async (values: FormikValues | TPassportUploadValues) => {
+ try {
+ await uploadPassport({
+ document_id: values.passportNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'passport',
+ expiration_date: values.passportExpiryDate,
+ file: values.passportFile,
+ });
+ await uploadSelfie(values, values.passportNumber);
+
+ return Promise.resolve();
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [documentIssuingCountryCode, uploadPassport, uploadSelfie]
+ );
+
+ return {
+ /** initial values for the passport and selfie forms */
+ initialValues,
+
+ /** `true` when passport/selfie upload encounter error */
+ isError,
+
+ /** `true` when passport/selfie are uploading */
+ isLoading,
+
+ /** `true` when passport/selfie are uploaded successfully */
+ isSuccess,
+
+ /** Reset upload statuses for passport and selfie */
+ resetUploadStatus,
+
+ /** upload passport and selfie files synchronously */
+ upload,
+ };
+};
+
+export default usePassportUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/index.ts
new file mode 100644
index 000000000000..6932af83d970
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/index.ts
@@ -0,0 +1 @@
+export { default as PassportUpload } from './PassportUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/index.ts
new file mode 100644
index 000000000000..017aabe41df6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/index.ts
@@ -0,0 +1 @@
+export * from './passportUploadValidators';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/passportUploadValidators.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/passportUploadValidators.ts
new file mode 100644
index 000000000000..659cd35abcb0
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/PassportUpload/utils/passportUploadValidators.ts
@@ -0,0 +1,13 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../../types';
+import { selfieUploadValidator } from '../../SelfieUpload/utils';
+import { fileValidator, getDocumentNumberValidator, getExpiryDateValidator } from '../../utils';
+
+export const getPassportUploadValidator = (localize: TTranslations['localize']) => {
+ return Yup.object().shape({
+ passportExpiryDate: getExpiryDateValidator(localize),
+ passportFile: fileValidator,
+ passportNumber: getDocumentNumberValidator('Passport', localize),
+ selfieFile: selfieUploadValidator,
+ });
+};
diff --git a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.scss b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.scss
similarity index 72%
rename from packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.scss
rename to packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.scss
index caab65f0feee..14f7160e8d56 100644
--- a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.scss
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.scss
@@ -1,12 +1,11 @@
-.wallets-selfie-document-upload {
+.wallets-selfie-upload {
width: fit-content;
- padding: 1.6rem;
display: flex;
flex-direction: column;
gap: 1.6rem;
- background: var(--light-8-primary-background, #fff);
+ padding: 1.6rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
@@ -14,7 +13,7 @@
min-width: 67.2rem;
min-height: 25.8rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
min-width: 100%;
min-height: 20.2rem;
}
@@ -22,13 +21,13 @@
& .wallets-dropzone {
min-height: 25.8rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
min-height: 20.2rem;
}
}
& .wallets-dropzone__content {
- @include desktop {
+ @include desktop-screen {
gap: 0.8rem;
}
}
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.tsx b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.tsx
new file mode 100644
index 000000000000..3331a9a630b1
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/SelfieUpload.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Dropzone, ModalStepWrapper, WalletText } from '../../../../../../components';
+import useDevice from '../../../../../../hooks/useDevice';
+import SelfieIcon from '../../../../../../public/images/accounts/selfie-icon.svg';
+import { Footer } from '../../../components';
+import { TManualDocumentComponent } from '../../utils';
+import { TSelfieUploadValues } from './types';
+import './SelfieUpload.scss';
+
+const SelfieUpload: TManualDocumentComponent = ({ onClickBack, onCompletion }) => {
+ const { localize } = useTranslations();
+ const { isDesktop } = useDevice();
+ const { dirty, errors, setFieldValue, values } = useFormikContext();
+
+ const isSelfieFormValid = dirty && !errors.selfieFile;
+
+ return (
+ (
+
+ )}
+ title={localize('Add a real MT5 account')}
+ >
+
+
+
+
+ }
+ noClick
+ onFileChange={(file?: File) => setFieldValue('selfieFile', file)}
+ />
+
+
+
+
+
+ );
+};
+
+export default SelfieUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/index.ts
new file mode 100644
index 000000000000..7cacb3c63a3b
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useSelfieUpload } from './useSelfieUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/useSelfieUpload.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/useSelfieUpload.ts
new file mode 100644
index 000000000000..bc946714d508
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/hooks/useSelfieUpload.ts
@@ -0,0 +1,48 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { useDocumentUpload } from '@deriv/api-v2';
+import { THooks } from '../../../../../../../types';
+import { TSelfieUploadValues } from '../types';
+
+const useSelfieUpload = (documentIssuingCountryCode: THooks.AccountSettings['country_code']) => {
+ const { resetStatus, status, upload: _upload } = useDocumentUpload();
+
+ const upload = useCallback(
+ async (values: FormikValues, documentNumber: string) => {
+ try {
+ const response = await _upload({
+ document_id: documentNumber,
+ document_issuing_country: documentIssuingCountryCode ?? undefined,
+ document_type: 'selfie_with_id',
+ file: values.selfieFile,
+ });
+ return Promise.resolve(response);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [_upload, documentIssuingCountryCode]
+ );
+
+ const initialValues = {} as TSelfieUploadValues;
+
+ return {
+ /** initial values for the selfie forms */
+ initialValues,
+
+ /** Function to reset selfie upload status */
+ resetStatus,
+
+ /** Selfie upload status */
+ status,
+
+ /**
+ * A function to upload selfie file for a particular document
+ * @param values FormikValues of the form
+ * @param documentNumber required to uniquely identify for which document is the selfie uploaded
+ * */
+ upload,
+ };
+};
+
+export default useSelfieUpload;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/index.ts
new file mode 100644
index 000000000000..268937396bcf
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/index.ts
@@ -0,0 +1,3 @@
+export * from './hooks';
+export { default as SelfieUpload } from './SelfieUpload';
+export * from './types';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/index.ts
new file mode 100644
index 000000000000..fcb073fefcd6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/types.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/types.ts
new file mode 100644
index 000000000000..18c2c2d96c30
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/types/types.ts
@@ -0,0 +1,3 @@
+export type TSelfieUploadValues = {
+ selfieFile?: File;
+};
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/index.ts
new file mode 100644
index 000000000000..6877f1302840
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/index.ts
@@ -0,0 +1 @@
+export * from './selfieUploadValidator';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/selfieUploadValidator.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/selfieUploadValidator.ts
new file mode 100644
index 000000000000..7c9e261c98d6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/SelfieUpload/utils/selfieUploadValidator.ts
@@ -0,0 +1,3 @@
+import { fileValidator } from '../../utils';
+
+export const selfieUploadValidator = fileValidator;
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/index.ts
new file mode 100644
index 000000000000..a48e5989a829
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/index.ts
@@ -0,0 +1,8 @@
+export * from './DocumentRules';
+export * from './DocumentSelection';
+export * from './DrivingLicenseUpload';
+export * from './IdentityCardUpload';
+export * from './ManualUploadErrorMessage';
+export * from './NIMCSlipUpload';
+export * from './PassportUpload';
+export * from './SelfieUpload';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/utils/index.ts
new file mode 100644
index 000000000000..8413b1c6a48c
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/utils/index.ts
@@ -0,0 +1 @@
+export * from './manualServiceValidators';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/components/utils/manualServiceValidators.ts b/packages/wallets/src/features/accounts/modules/ManualService/components/utils/manualServiceValidators.ts
new file mode 100644
index 000000000000..9d6df904f944
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/components/utils/manualServiceValidators.ts
@@ -0,0 +1,55 @@
+import moment from 'moment';
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../../types';
+
+export const documentRequiredValidator = (documentName: string, localize: TTranslations['localize']) =>
+ Yup.string().required(localize('{{name}} is required', { name: documentName }));
+
+export const getExpiryDateValidator = (localize: TTranslations['localize']) => {
+ return Yup.string()
+ .nullable()
+ .required(localize('Expiry date is required.'))
+ .test({
+ name: 'test-expiry-date-is-null',
+ test: (value, context) => {
+ if (value === null) {
+ return context.createError({ message: localize('Expiry date is required.') });
+ }
+ return true;
+ },
+ })
+ .test({
+ name: 'test-min-expiry-date',
+ test: (value, context) => {
+ if (moment(value).isBefore(new Date())) {
+ return context.createError({
+ message: localize('Expiry date cannot be today date or in the past'),
+ });
+ }
+ return true;
+ },
+ });
+};
+
+export const getDocumentNumberValidator = (documentName: string, localize: TTranslations['localize']) => {
+ return Yup.string()
+ .required(
+ localize('{{name}} number is required.', {
+ name: documentName,
+ })
+ )
+ .max(
+ 30,
+ localize('{{name}} number must be less than 30 characters.', {
+ name: documentName,
+ })
+ )
+ .matches(
+ new RegExp(/^[\w\s-]{0,30}$/g),
+ localize('Only letters, numbers, space, underscore, and hyphen are allowed for {{name}} number.', {
+ name: documentName,
+ })
+ );
+};
+
+export const fileValidator = Yup.mixed().required();
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/index.ts
new file mode 100644
index 000000000000..9e11892b5f2e
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/index.ts
@@ -0,0 +1 @@
+export { default as ManualService } from './ManualService';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/utils/index.ts b/packages/wallets/src/features/accounts/modules/ManualService/utils/index.ts
new file mode 100644
index 000000000000..04bca77e0dec
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/utils/index.ts
@@ -0,0 +1 @@
+export * from './utils';
diff --git a/packages/wallets/src/features/accounts/modules/ManualService/utils/utils.tsx b/packages/wallets/src/features/accounts/modules/ManualService/utils/utils.tsx
new file mode 100644
index 000000000000..1b64e313c833
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/ManualService/utils/utils.tsx
@@ -0,0 +1,102 @@
+import React from 'react';
+import ClearPhoto from '../../../../../public/images/accounts/clear-photo.svg';
+import ClockIcon from '../../../../../public/images/accounts/clock-icon.svg';
+import DrivingLicenseIcon from '../../../../../public/images/accounts/driving-license.svg';
+import IdentityCardIcon from '../../../../../public/images/accounts/identity-card.svg';
+import ImageIcon from '../../../../../public/images/accounts/image-icon.svg';
+import LessThanEightIcon from '../../../../../public/images/accounts/less-than-eight-icon.svg';
+import NIMCSlipIcon from '../../../../../public/images/accounts/nimc-slip.svg';
+import PassportIcon from '../../../../../public/images/accounts/passport.svg';
+import { THooks, TTranslations } from '../../../../../types';
+import { DrivingLicenseUpload, IdentityCardUpload, NIMCSlipUpload, PassportUpload } from '../components';
+
+type TManualDocumentComponentProps = {
+ // eslint-disable-next-line lines-around-comment
+ /** clients country code which is required to be passed during document upload */
+ documentIssuingCountryCode?: THooks.AccountSettings['country_code'];
+
+ /** used to go back to the manual document selection page from the document upload page */
+ onClickBack?: VoidFunction;
+
+ /** callback to be called after successful completion of manual document upload */
+ onCompletion?: VoidFunction;
+};
+
+export type TManualDocumentComponent = React.FC;
+
+export type TManualDocumentType = Record<
+ string,
+ {
+ component: TManualDocumentComponent;
+ countries?: string[];
+ description: string;
+ icon: React.ComponentType>;
+ title: string;
+ }
+>;
+
+export type TDocumentRule = {
+ description: string;
+ icon: JSX.Element;
+};
+
+type TGetManualDocumentsMapper = {
+ [k: string]: TManualDocumentType[number];
+};
+
+/** A mapper which contains the info on all the available manual POI upload options for a client */
+export const getManualDocumentsMapper = (localize: TTranslations['localize']) =>
+ ({
+ passport: {
+ component: PassportUpload,
+ description: localize('Upload the page that contains your photo.'),
+ icon: PassportIcon,
+ title: localize('Passport'),
+ },
+ // eslint-disable-next-line sort-keys
+ 'driving-license': {
+ component: DrivingLicenseUpload,
+ description: localize('Upload the front and back of your driving licence.'),
+ icon: DrivingLicenseIcon,
+ title: localize('Driving licence'),
+ },
+ 'identity-card': {
+ component: IdentityCardUpload,
+ description: localize('Upload the front and back of your identity card.'),
+ icon: IdentityCardIcon,
+ title: localize('Identity card'),
+ },
+ 'nimc-slip': {
+ component: NIMCSlipUpload,
+ countries: ['ng'],
+ description: localize('Upload the front and back of your identity card.'),
+ icon: NIMCSlipIcon,
+ title: localize('NIMC slip and proof of age'),
+ },
+ } as TGetManualDocumentsMapper);
+
+const getDocumentRules = (localize: TTranslations['localize']) =>
+ [
+ {
+ description: localize('Must be valid for at least 6 months'),
+ icon: ,
+ },
+ {
+ description: localize('A clear colour photo or scanned image'),
+ icon: ,
+ },
+ {
+ description: localize('JPEG, JPG, PNG, PDF, or GIF'),
+ icon: ,
+ },
+ {
+ description: localize('Less than 8MB'),
+ icon: ,
+ },
+ ] as TDocumentRule[];
+
+/** Special rules to show as hints for NIMC countries */
+export const getNIMCDocumentRules = (localize: TTranslations['localize']) => getDocumentRules(localize).slice(1);
+
+/** General rules to show as hints for non-NIMC countries */
+export const getGeneralDocumentRules = (localize: TTranslations['localize']) => getDocumentRules(localize);
diff --git a/packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.scss b/packages/wallets/src/features/accounts/modules/Poa/Poa.scss
similarity index 90%
rename from packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.scss
rename to packages/wallets/src/features/accounts/modules/Poa/Poa.scss
index 79754657abef..1ed64cee8521 100644
--- a/packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.scss
+++ b/packages/wallets/src/features/accounts/modules/Poa/Poa.scss
@@ -11,7 +11,7 @@
background: var(--system-light-8-primary-background, #fff);
overflow-y: auto;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
height: 100%;
padding: 1.6rem;
diff --git a/packages/wallets/src/features/accounts/modules/Poa/Poa.tsx b/packages/wallets/src/features/accounts/modules/Poa/Poa.tsx
new file mode 100644
index 000000000000..4fe20e4f025e
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/Poa.tsx
@@ -0,0 +1,82 @@
+import React, { useState } from 'react';
+import { Formik, FormikValues } from 'formik';
+import { useTranslations } from '@deriv-com/translations';
+import { InlineMessage, Loader, Text } from '@deriv-com/ui';
+import { ModalStepWrapper } from '../../../../components';
+import { THooks } from '../../../../types';
+import { Footer } from '../components';
+import { AddressSection, DocumentSubmission, PoaUploadErrorMessage } from './components';
+import { usePoa } from './hooks';
+import { getPoaValidationSchema } from './utils';
+import './Poa.scss';
+
+type TPoaProps = {
+ onCompletion?: VoidFunction;
+};
+
+const Poa: React.FC = ({ onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ errorSettings,
+ initialStatus,
+ initialValues,
+ isLoading,
+ isSuccess: isSubmissionSuccess,
+ resetError,
+ upload: upload_,
+ } = usePoa();
+ const [errorDocumentUpload, setErrorDocumentUpload] = useState();
+
+ if (isLoading) return ;
+
+ if (isSubmissionSuccess && onCompletion) {
+ onCompletion();
+ }
+
+ const upload = async (values: FormikValues) => {
+ try {
+ await upload_(values);
+ } catch (error) {
+ setErrorDocumentUpload((error as THooks.DocumentUpload).error);
+ }
+ };
+
+ return (
+
+ {({ handleSubmit, isValid, resetForm }) => {
+ const onErrorRetry = () => {
+ resetForm();
+ resetError();
+ };
+
+ if (errorDocumentUpload) {
+ return ;
+ }
+
+ return (
+ }
+ title={localize('Add a real MT5 account')}
+ >
+
+ {errorSettings?.message && (
+
+ {localize(errorSettings.message)}
+
+ )}
+
+
+
+
+ );
+ }}
+
+ );
+};
+
+export default Poa;
diff --git a/packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.scss b/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.scss
similarity index 84%
rename from packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.scss
rename to packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.scss
index 5244780939dc..316ec745a5a8 100644
--- a/packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.scss
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.scss
@@ -5,7 +5,7 @@
gap: 1.6rem;
max-width: 85rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
@@ -16,7 +16,7 @@
align-items: center;
gap: 1.1rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
display: flex;
align-items: center;
gap: 0.8rem;
@@ -29,7 +29,7 @@
flex-shrink: 0;
background: var(--system-light-7-secondary-background, #f2f3f4);
- @include mobile {
+ @include mobile-or-tablet-screen {
height: 0.1rem;
flex: 1 0 0;
}
@@ -49,7 +49,7 @@
&-message {
width: 80rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
}
@@ -61,7 +61,7 @@
flex-direction: column;
gap: 1.6rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
}
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.tsx b/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.tsx
new file mode 100644
index 000000000000..ae8de4cfca87
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/AddressSection.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { FormDropdown, FormField, InlineMessage, WalletText } from '../../../../../../components';
+import { TAddressDetails } from '../../types';
+import './AddressSection.scss';
+
+const AddressSection: React.FC = () => {
+ const { localize } = useTranslations();
+ const { status } = useFormikContext();
+
+ return (
+
+ );
+};
+
+export default AddressSection;
diff --git a/packages/wallets/src/features/accounts/screens/AddressSection/index.ts b/packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/AddressSection/index.ts
rename to packages/wallets/src/features/accounts/modules/Poa/components/AddressSection/index.ts
diff --git a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.scss b/packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.scss
similarity index 88%
rename from packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.scss
rename to packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.scss
index bc29e9d14a9a..7e34b04220cf 100644
--- a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.scss
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.scss
@@ -6,9 +6,9 @@
grid-template-columns: repeat(3, 1fr);
gap: 4.5rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
display: flex;
- flex-direction: column;
+ flex-flow: row wrap;
align-items: center;
justify-content: center;
}
@@ -26,7 +26,7 @@
width: 23.7rem;
height: 21.8rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
margin-inline: auto;
}
}
diff --git a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.tsx b/packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.tsx
similarity index 80%
rename from packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.tsx
rename to packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.tsx
index 962d9388c41f..ff8ec0c69243 100644
--- a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/CommonMistakesExamples.tsx
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/CommonMistakesExamples.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { WalletText } from '../../../../components/Base';
-import StatusLoss from '../../../../public/images/status-loss.svg';
+import { WalletText } from '../../../../../../components/Base';
+import StatusLoss from '../../../../../../public/images/status-loss.svg';
import './CommonMistakesExamples.scss';
type TCommonMistakeExamplePartialsProps = {
diff --git a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/index.ts b/packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/CommonMistakesExamples/index.ts
rename to packages/wallets/src/features/accounts/modules/Poa/components/CommonMistakesExamples/index.ts
diff --git a/packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.scss b/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.scss
similarity index 89%
rename from packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.scss
rename to packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.scss
index eee7ef3fe24d..d7e5f42698c4 100644
--- a/packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.scss
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.scss
@@ -5,7 +5,7 @@
gap: 2.4rem;
max-width: 85rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
@@ -16,7 +16,7 @@
align-items: center;
gap: 1.1rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
display: flex;
align-items: center;
gap: 0.8rem;
@@ -29,7 +29,7 @@
flex-shrink: 0;
background: var(--system-light-7-secondary-background, #f2f3f4);
- @include mobile {
+ @include mobile-or-tablet-screen {
height: 0.1rem;
flex: 1 0 0;
}
@@ -46,7 +46,7 @@
border-radius: 0.8rem;
border: 0.1rem solid var(--system-light-5-active-background, #d6dadb);
- @include mobile {
+ @include mobile-or-tablet-screen {
border: none;
padding: 0;
}
@@ -79,10 +79,13 @@
align-self: stretch;
height: 16rem;
border-radius: 0.8rem;
- border: 0.1rem dashed var(--system-light-5-active-background, #d6dadb);
cursor: pointer;
}
+ & .wallets-dropzone__error {
+ min-height: 0;
+ }
+
& .wallets-dropzone__placeholder {
gap: 2rem;
}
@@ -99,7 +102,7 @@
align-self: stretch;
& > .wallets-text {
- @include mobile {
+ @include mobile-or-tablet-screen {
max-width: 18rem;
}
}
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.tsx b/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.tsx
new file mode 100644
index 000000000000..0ee81e6817ba
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/DocumentSubmission.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { useIsEuRegion } from '@deriv/api-v2';
+import { LabelPairedArrowUpFromBracketXlFillIcon } from '@deriv/quill-icons';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Dropzone, WalletText } from '../../../../../../components';
+import useDevice from '../../../../../../hooks/useDevice';
+import { TDocumentSubmission } from '../../types';
+import { getExampleImagesConfig } from '../../utils';
+import { CommonMistakesExamples } from '../CommonMistakesExamples';
+import './DocumentSubmission.scss';
+
+const DocumentSubmission: React.FC = () => {
+ const { localize } = useTranslations();
+ const { isDesktop } = useDevice();
+ const { data: isEuRegion } = useIsEuRegion();
+ const { setFieldValue, values } = useFormikContext();
+
+ const listItems = [
+ localize('Utility bill: electricity, water, gas, or landline phone bill.'),
+ localize(
+ 'Financial, legal, or government document: recent bank statement, affidavit, or government-issued letter.'
+ ),
+ localize('Home rental agreement: valid and current agreement.'),
+ ];
+
+ return (
+
+
+
+
+
+ {localize(
+ 'We accept only these types of documents as proof of address. The document must be recent (issued within last {{timePeriod}} months) and include your name and address:',
+ { timePeriod: isEuRegion ? '6' : '12' }
+ )}
+
+
+
+ {listItems.map(item => (
+
+ {item}
+
+ ))}
+
+
+
+
+
+
+
+
+ {getExampleImagesConfig().map(config => (
+ }
+ key={`common-mistake-${config.description}`}
+ />
+ ))}
+
+
+
+
+
+
+
}
+ maxSize={8388608}
+ onFileChange={(file?: File) => setFieldValue('poaFile', file)}
+ title={localize('Drag and drop a file or click to browse your files.')}
+ titleType='bold'
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default DocumentSubmission;
diff --git a/packages/wallets/src/features/accounts/screens/DocumentSubmission/index.ts b/packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/DocumentSubmission/index.ts
rename to packages/wallets/src/features/accounts/modules/Poa/components/DocumentSubmission/index.ts
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.scss b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.scss
new file mode 100644
index 000000000000..7c3b97051129
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.scss
@@ -0,0 +1,16 @@
+.wallets-poa-upload-error-message {
+ width: 100%;
+ min-width: 99.6rem;
+ height: 100%;
+ min-height: calc(var(--wallets-vh, 1vh) * 100 - 30rem);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ @include mobile-or-tablet-screen {
+ width: 100%;
+ height: 100%;
+ padding: auto 1.6rem;
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.tsx b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.tsx
new file mode 100644
index 000000000000..d28573b694fb
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/PoaUploadErrorMessage.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { DerivLightDeclinedPoaIcon } from '@deriv/quill-icons';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { ActionScreen, Button } from '@deriv-com/ui';
+import { ModalStepWrapper } from '../../../../../../components';
+import './PoaUploadErrorMessage.scss';
+
+type TPoaUploadErrorMessage = {
+ errorCode: string;
+ onRetry: VoidFunction;
+};
+
+const PoaUploadErrorMessage: React.FC = ({ errorCode, onRetry }) => {
+ const { localize } = useTranslations();
+
+ const errorCodeToDescriptionMapper: Record = {
+ DuplicateUpload: localize("It seems you've submitted this document before. Upload a new document."),
+ } as const;
+
+ return (
+
+
+
+
+
+ }
+ description={errorCodeToDescriptionMapper[errorCode]}
+ icon={ }
+ title={localize('Proof of address documents upload failed')}
+ />
+
+
+ );
+};
+
+export default PoaUploadErrorMessage;
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/index.ts b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/index.ts
new file mode 100644
index 000000000000..cdef06c767bf
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/PoaUploadErrorMessage/index.ts
@@ -0,0 +1 @@
+export { default as PoaUploadErrorMessage } from './PoaUploadErrorMessage';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/components/index.ts b/packages/wallets/src/features/accounts/modules/Poa/components/index.ts
new file mode 100644
index 000000000000..aea838878bca
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/components/index.ts
@@ -0,0 +1,3 @@
+export * from './AddressSection';
+export * from './DocumentSubmission';
+export * from './PoaUploadErrorMessage';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/hooks/index.ts b/packages/wallets/src/features/accounts/modules/Poa/hooks/index.ts
new file mode 100644
index 000000000000..3dd0783dfa1b
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/hooks/index.ts
@@ -0,0 +1 @@
+export { default as usePoa } from './usePoa';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/hooks/usePoa.ts b/packages/wallets/src/features/accounts/modules/Poa/hooks/usePoa.ts
new file mode 100644
index 000000000000..7cb98630e3a1
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/hooks/usePoa.ts
@@ -0,0 +1,120 @@
+import { useCallback, useState } from 'react';
+import { FormikValues } from 'formik';
+import { DocumentUploadStatus, useDocumentUpload, useSettings, useStatesList } from '@deriv/api-v2';
+import { TAddressDetails, TDocumentSubmission } from '../types';
+
+type TPoaValues = TAddressDetails & TDocumentSubmission;
+
+const usePoa = () => {
+ const {
+ data: settings,
+ error: errorSettings,
+ isLoading: isSettingsLoading,
+ isSuccess: isSettingsUpdateSuccess,
+ update: updateSettings,
+ } = useSettings();
+ const country = settings?.country_code ?? '';
+ const { data: statesList, isLoading: isStatesListLoading } = useStatesList(country);
+ const {
+ resetStatus: resetDocumentUploadStatus,
+ status: documentUploadStatus,
+ upload: uploadDocument,
+ } = useDocumentUpload();
+ const [isSubmissionInitiated, setIsSubmissionInitiated] = useState(false);
+
+ const isDocumentUploading = documentUploadStatus === DocumentUploadStatus.LOADING;
+ const isDocumentUploadSuccess = documentUploadStatus === DocumentUploadStatus.SUCCESS;
+ const isLoading = isDocumentUploading || isSettingsLoading || isStatesListLoading;
+
+ const initialValues = {
+ firstLine: settings.address_line_1,
+ secondLine: settings.address_line_2,
+ stateProvinceLine: settings.address_state,
+ townCityLine: settings.address_city,
+ zipCodeLine: settings.address_postcode,
+ } as TPoaValues;
+
+ const initialStatus = {
+ statesList,
+ };
+
+ const resetError = () => {
+ resetDocumentUploadStatus(DocumentUploadStatus.IDLE);
+ };
+
+ // since we call get_settings initially, isSubmissionSuccess helps us to distinguish
+ // between the initial call and the upload call using the isSubmissionInitiated flag state
+ // which is set only when user initiates submission
+ const isSubmissionSuccess = isSubmissionInitiated && isDocumentUploadSuccess && isSettingsUpdateSuccess;
+
+ const upload = useCallback(
+ async (values: FormikValues | TPoaValues) => {
+ const isAddressDetailsFormDirty =
+ values.firstLine !== settings.address_line_1 ||
+ values.secondLine !== settings.address_line_2 ||
+ values.stateProvinceLine !== settings.address_state ||
+ values.townCityLine !== settings.address_city ||
+ values.zipCodeLine !== settings.address_postcode;
+
+ if (isAddressDetailsFormDirty) {
+ // update address details using set_settings call, only if the form is dirty
+ updateSettings({
+ address_city: values.townCityLine,
+ address_line_1: values.firstLine,
+ address_line_2: values.secondLine,
+ address_postcode: values.zipCodeLine,
+ address_state: values.stateProvinceLine,
+ });
+ }
+
+ try {
+ // upload POA document using document_upload
+ await uploadDocument({
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'proofaddress',
+ file: values.poaFile,
+ });
+
+ setIsSubmissionInitiated(true);
+ return Promise.resolve();
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ },
+ [
+ settings.address_city,
+ settings.address_line_1,
+ settings.address_line_2,
+ settings.address_postcode,
+ settings.address_state,
+ settings?.country_code,
+ updateSettings,
+ uploadDocument,
+ ]
+ );
+
+ return {
+ /** Error returned from API calls for address details update and POA document upload */
+ errorSettings,
+
+ /** Contains the shared countryList data which is shared between the AddressSection and DocumentSubmission components through the status object from Formik context*/
+ initialStatus,
+
+ /** Initial values for the POA form */
+ initialValues,
+
+ /** `true` if data required for initial render is loading */
+ isLoading,
+
+ /** `true` if the address details and document upload is successful */
+ isSuccess: isSubmissionSuccess,
+
+ /** reset error for API response */
+ resetError,
+
+ /** Function to initiate upload of address details and document */
+ upload,
+ };
+};
+//
+export default usePoa;
diff --git a/packages/wallets/src/features/accounts/modules/Poa/index.ts b/packages/wallets/src/features/accounts/modules/Poa/index.ts
new file mode 100644
index 000000000000..ce5f5db520a9
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/index.ts
@@ -0,0 +1 @@
+export { default as Poa } from './Poa';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/types/index.ts b/packages/wallets/src/features/accounts/modules/Poa/types/index.ts
new file mode 100644
index 000000000000..fcb073fefcd6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/types/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/types/types.ts b/packages/wallets/src/features/accounts/modules/Poa/types/types.ts
new file mode 100644
index 000000000000..817df10b0be6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/types/types.ts
@@ -0,0 +1,11 @@
+import { THooks } from '../../../../../types';
+
+export type TAddressDetails = {
+ firstLine: THooks.AccountSettings['address_line_1'];
+ secondLine: THooks.AccountSettings['address_line_2'];
+ stateProvinceLine: THooks.AccountSettings['address_state'];
+ townCityLine: THooks.AccountSettings['address_city'];
+ zipCodeLine: THooks.AccountSettings['address_postcode'];
+};
+
+export type TDocumentSubmission = { poaFile?: File };
diff --git a/packages/wallets/src/features/accounts/modules/Poa/utils/constants.tsx b/packages/wallets/src/features/accounts/modules/Poa/utils/constants.tsx
new file mode 100644
index 000000000000..5f5d265848ef
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/utils/constants.tsx
@@ -0,0 +1,41 @@
+import {
+ DerivLightIcBlurryDocumentIcon,
+ DerivLightIcCroppedDocumentIcon,
+ DerivLightIcDocumentAddressMismatchIcon,
+ DerivLightIcDocumentNameMismatchIcon,
+ DerivLightIcEnvelopeIcon,
+ DerivLightIcOldIssuedDocumentMoreThan12Icon,
+} from '@deriv/quill-icons';
+import { localize } from '@deriv-com/translations';
+
+type TExampleImageConfig = {
+ description: React.ReactNode;
+ image: React.ComponentType>;
+};
+
+export const getExampleImagesConfig = (): TExampleImageConfig[] => [
+ {
+ description: localize('Name in document doesn’t match your Deriv profile.'),
+ image: DerivLightIcDocumentNameMismatchIcon,
+ },
+ {
+ description: localize('Address in document doesn’t match address you entered above.'),
+ image: DerivLightIcDocumentAddressMismatchIcon,
+ },
+ {
+ description: localize('Document issued more than 12-months ago.'),
+ image: DerivLightIcOldIssuedDocumentMoreThan12Icon,
+ },
+ {
+ description: localize('Blurry document. All information must be clear and visible.'),
+ image: DerivLightIcBlurryDocumentIcon,
+ },
+ {
+ description: localize('Cropped document. All information must be clear and visible.'),
+ image: DerivLightIcCroppedDocumentIcon,
+ },
+ {
+ description: localize('An envelope with your name and address.'),
+ image: DerivLightIcEnvelopeIcon,
+ },
+];
diff --git a/packages/wallets/src/features/accounts/modules/Poa/utils/index.ts b/packages/wallets/src/features/accounts/modules/Poa/utils/index.ts
new file mode 100644
index 000000000000..06ba5afd12f3
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './constants';
+export * from './poaValidationSchema';
diff --git a/packages/wallets/src/features/accounts/modules/Poa/utils/poaValidationSchema.ts b/packages/wallets/src/features/accounts/modules/Poa/utils/poaValidationSchema.ts
new file mode 100644
index 000000000000..a9a4b8e47448
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poa/utils/poaValidationSchema.ts
@@ -0,0 +1,30 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../types';
+import { fileValidator } from '../../ManualService/components/utils';
+
+export const getPoaValidationSchema = (localize: TTranslations['localize']) =>
+ Yup.object().shape({
+ firstLine: Yup.string()
+ .trim()
+ .required(localize('First line of address is required.'))
+ .max(70, localize('Should be less than 70.'))
+ .matches(
+ /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u,
+ localize("Use only the following special characters: . , ' : ; ( ) ° @ # / -'")
+ ),
+ poaFile: fileValidator,
+ secondLine: Yup.string()
+ .trim()
+ .max(70, localize('Should be less than 70.'))
+ .matches(
+ /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u,
+ localize("Use only the following special characters: . , ' : ; ( ) ° @ # / -'")
+ ),
+ townCityLine: Yup.string()
+ .required(localize('Town/City is required.'))
+ .max(70, localize('Should be less than 70.'))
+ .matches(/^[a-zA-Z\s\-.']+$/, localize('Only letters, space, hyphen, period, and apostrophe are allowed.')),
+ zipCodeLine: Yup.string()
+ .max(20, localize('Please enter a Postal/ZIP code under 20 characters.'))
+ .matches(/^[A-Za-z0-9][A-Za-z0-9\s-]*$/, localize('Only letters, numbers, space, and hyphen are allowed.')),
+ });
diff --git a/packages/wallets/src/features/accounts/modules/Poi/Poi.tsx b/packages/wallets/src/features/accounts/modules/Poi/Poi.tsx
new file mode 100644
index 000000000000..7f5461bb1f76
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poi/Poi.tsx
@@ -0,0 +1,36 @@
+import React, { useState } from 'react';
+import { usePOI } from '@deriv/api-v2';
+import { Loader } from '@deriv-com/ui';
+import { THooks } from '../../../../types';
+import { DocumentService } from '../DocumentService';
+import { ManualService } from '../ManualService';
+
+type TPoiProps = {
+ onCompletion?: VoidFunction;
+};
+
+const Poi: React.FC = ({ onCompletion }) => {
+ const { data: poiData, isLoading } = usePOI();
+ const [renderManualService, setRenderManualService] = useState(false);
+
+ if (isLoading) {
+ return ;
+ }
+
+ const service = poiData?.current.service as THooks.POI['current']['service'];
+
+ if (service === 'manual' || renderManualService) {
+ return ;
+ }
+
+ return (
+ {
+ setRenderManualService(true);
+ }}
+ />
+ );
+};
+
+export default Poi;
diff --git a/packages/wallets/src/features/accounts/modules/Poi/index.ts b/packages/wallets/src/features/accounts/modules/Poi/index.ts
new file mode 100644
index 000000000000..8453c9af6c41
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/Poi/index.ts
@@ -0,0 +1 @@
+export { default as Poi } from './Poi';
diff --git a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.scss b/packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.scss
similarity index 73%
rename from packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.scss
rename to packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.scss
index bd65a730e3dc..28d1eada765c 100644
--- a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.scss
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.scss
@@ -1,4 +1,4 @@
-.wallets-personal-details {
+.wallets-tax-information {
min-width: 93.6rem;
min-height: 50.2rem;
display: flex;
@@ -8,7 +8,7 @@
background: var(--system-light-8-primary-background, #fff);
padding: 2.4rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
min-width: 100%;
min-height: 100%;
}
@@ -19,7 +19,7 @@
flex-direction: column;
gap: 2.4rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
gap: 1.6rem;
}
}
@@ -28,24 +28,18 @@
width: 100%;
display: flex;
flex-direction: column;
- align-items: center;
gap: 1.6rem;
margin-top: 0.8rem;
- @include desktop {
+ @include desktop-screen {
max-width: 40rem;
}
}
- &__dropdown {
- width: 100%;
- padding-bottom: 2rem;
- }
-
- &__inline {
+ &__message {
width: 40rem;
- @include mobile {
+ @include mobile-or-tablet-screen {
width: 100%;
}
}
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.tsx b/packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.tsx
new file mode 100644
index 000000000000..3948425583a6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/TaxInformation.tsx
@@ -0,0 +1,121 @@
+import React, { useEffect } from 'react';
+import { Formik } from 'formik';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Loader } from '@deriv-com/ui';
+import { FormDropdown, FormField, ModalStepWrapper, WalletText } from '../../../../components';
+import { Footer } from '../components';
+import { NeedHelpMessage } from './components';
+import { useTaxInformation } from './hooks';
+import {
+ getAccountOpeningReasonList,
+ getTaxInformationValidationSchema,
+ getTaxResidenceValidator,
+ getTinValidator,
+} from './utils';
+import './TaxInformation.scss';
+
+type TTaxInformationProps = {
+ onCompletion?: VoidFunction;
+};
+
+const TaxInformation: React.FC = ({ onCompletion }) => {
+ const { localize } = useTranslations();
+ const {
+ countryCodeToPatternMapper,
+ countryList,
+ initialValues,
+ isLoading,
+ isSubmitted: isTaxInformationSubmitted,
+ onSubmit,
+ } = useTaxInformation();
+
+ useEffect(() => {
+ if (isTaxInformationSubmitted && onCompletion) {
+ onCompletion();
+ }
+ }, [isTaxInformationSubmitted, onCompletion]);
+
+ return (
+
+ {({ errors, handleSubmit, isValid, values }) => {
+ return (
+ }
+ title={localize('Add a real MT5 account')}
+ >
+
+ {isLoading &&
}
+ {!isLoading && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+ }}
+
+ );
+};
+
+export default TaxInformation;
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/NeedHelpMessage.tsx b/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/NeedHelpMessage.tsx
new file mode 100644
index 000000000000..68bb08b84a8b
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/NeedHelpMessage.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
+import { InlineMessage, WalletText } from '../../../../../../components';
+
+const NeedHelpMessage = () => {
+ const onClickLiveChat = () => window.LC_API.open_chat_window();
+
+ return (
+
+
+
+ ,
+ ]}
+ i18n_default_text='Need help with tax info? Let us know via <0>live chat0>.'
+ />
+
+
+
+ );
+};
+
+export default NeedHelpMessage;
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/index.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/index.ts
new file mode 100644
index 000000000000..9250879c672d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/components/NeedHelpMessage/index.ts
@@ -0,0 +1 @@
+export { default as NeedHelpMessage } from './NeedHelpMessage';
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/components/index.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/components/index.ts
new file mode 100644
index 000000000000..e9657d913ec1
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/components/index.ts
@@ -0,0 +1 @@
+export * from './NeedHelpMessage';
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/index.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/index.ts
new file mode 100644
index 000000000000..e406b3b954f7
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useTaxInformation } from './useTaxInformation';
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/useTaxInformation.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/useTaxInformation.ts
new file mode 100644
index 000000000000..0332060125e6
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/hooks/useTaxInformation.ts
@@ -0,0 +1,91 @@
+import { useEffect, useMemo, useState } from 'react';
+import { FormikValues } from 'formik';
+import { useResidenceList, useSettings } from '@deriv/api-v2';
+import { useTranslations } from '@deriv-com/translations';
+import { THooks } from '../../../../../types';
+
+type TTaxInformationValues = {
+ accountOpeningReason: THooks.AccountSettings['account_opening_reason'];
+ citizenship: THooks.AccountSettings['citizen'];
+ placeOfBirth: THooks.AccountSettings['place_of_birth'];
+ taxIdentificationNumber: THooks.AccountSettings['tax_identification_number'];
+ taxResidence: THooks.AccountSettings['tax_residence'];
+};
+
+const useTaxInformation = () => {
+ const { localize } = useTranslations();
+ const { data: residenceList, isLoading, isSuccess: isResidenceListSuccess } = useResidenceList();
+ const { data: accountSettings, error, isSuccess: isAccountSettingsSuccess, update } = useSettings();
+ const [isSubmissionInitiated, setIsSubmissionInitiated] = useState(false);
+ const [isSubmitted, setIsSubmitted] = useState(false);
+
+ const countryCodeToPatternMapper = useMemo(() => {
+ const countryCodeToPatternMapping: Record = {};
+
+ if (isResidenceListSuccess) {
+ residenceList.forEach(residence => {
+ if (residence.value && !(residence.value in countryCodeToPatternMapping)) {
+ countryCodeToPatternMapping[residence.value] = residence?.tin_format?.[0] ?? '';
+ }
+ });
+ }
+ return countryCodeToPatternMapping;
+ }, [isResidenceListSuccess, residenceList]);
+
+ const countryList = useMemo(() => {
+ return residenceList.map(residence => ({
+ text: residence.text ? localize(residence.text) : '',
+ value: residence.value as string,
+ }));
+ }, [localize, residenceList]);
+
+ const initialValues = {
+ accountOpeningReason: accountSettings.account_opening_reason
+ ? localize(accountSettings.account_opening_reason)
+ : undefined,
+ citizenship: accountSettings.citizen ? localize(accountSettings.citizen) : undefined,
+ placeOfBirth: accountSettings.place_of_birth ? localize(accountSettings.place_of_birth) : undefined,
+ taxIdentificationNumber: accountSettings.tax_identification_number
+ ? localize(accountSettings.tax_identification_number)
+ : undefined,
+ taxResidence: accountSettings.tax_residence ? localize(accountSettings.tax_residence) : undefined,
+ } as TTaxInformationValues;
+
+ const onSubmit = (values: FormikValues | TTaxInformationValues) => {
+ if (
+ values &&
+ values.placeOfBirth &&
+ values.taxResidence &&
+ values.accountOpeningReason &&
+ values.taxIdentificationNumber
+ ) {
+ update({
+ account_opening_reason: values.accountOpeningReason,
+ citizen: values.citizenship,
+ place_of_birth: values.placeOfBirth,
+ tax_identification_number: values.taxIdentificationNumber,
+ tax_residence: values.taxResidence,
+ });
+ setIsSubmissionInitiated(true);
+ }
+ };
+
+ useEffect(() => {
+ if (isSubmissionInitiated && isAccountSettingsSuccess) {
+ setIsSubmissionInitiated(false);
+ setIsSubmitted(true);
+ }
+ }, [isAccountSettingsSuccess, isSubmissionInitiated]);
+
+ return {
+ countryCodeToPatternMapper,
+ countryList,
+ error,
+ initialValues,
+ isLoading,
+ isSubmitted,
+ onSubmit,
+ };
+};
+
+export default useTaxInformation;
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/index.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/index.ts
new file mode 100644
index 000000000000..84e5b66585c5
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/index.ts
@@ -0,0 +1 @@
+export { default as TaxInformation } from './TaxInformation';
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/utils/constants.tsx b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/constants.tsx
new file mode 100644
index 000000000000..c4c4d99f91c9
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/constants.tsx
@@ -0,0 +1,20 @@
+import { TTranslations } from '../../../../../types';
+
+export const getAccountOpeningReasonList = (localize: TTranslations['localize']) => [
+ {
+ text: localize('Hedging'),
+ value: 'Hedging',
+ },
+ {
+ text: localize('Income Earning'),
+ value: 'Income Earning',
+ },
+ {
+ text: localize('Speculative'),
+ value: 'Speculative',
+ },
+ {
+ text: localize('Peer-to-peer exchange'),
+ value: 'Peer-to-peer exchange',
+ },
+];
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/utils/index.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/index.ts
new file mode 100644
index 000000000000..8dce2ce56ef9
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './constants';
+export * from './taxInformationValidator';
diff --git a/packages/wallets/src/features/accounts/modules/TaxInformation/utils/taxInformationValidator.ts b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/taxInformationValidator.ts
new file mode 100644
index 000000000000..8dfc022139fa
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/TaxInformation/utils/taxInformationValidator.ts
@@ -0,0 +1,36 @@
+import * as Yup from 'yup';
+import { TTranslations } from '../../../../../types';
+
+export const getTinValidator = (pattern: string, localize: TTranslations['localize']) => {
+ if (pattern && pattern.length > 0) {
+ return Yup.string()
+ .required(localize('Please fill in Tax identification number.'))
+ .matches(new RegExp(pattern), localize('The format is incorrect.'));
+ }
+ return Yup.string().required(localize('Please fill in Tax identification number.'));
+};
+
+export const getTaxResidenceValidator = (
+ countryList: Record[],
+ localize: TTranslations['localize']
+) => {
+ return Yup.string()
+ .required(localize('Tax residence is required.'))
+ .test({
+ name: 'test-tax-residence',
+ test: (value, context) => {
+ const countryFound = value && countryList.find(country => country.value.toLowerCase() === value);
+ if (!countryFound) {
+ return context.createError({ message: localize('Please enter a valid country.') });
+ }
+ return true;
+ },
+ });
+};
+
+export const getTaxInformationValidationSchema = (localize: TTranslations['localize']) =>
+ Yup.object().shape({
+ accountOpeningReason: Yup.string().required(localize('Account opening reason is required.')),
+ citizenship: Yup.string().required(localize('Citizenship is required.')),
+ placeOfBirth: Yup.string().required(localize('Place of birth is required.')),
+ });
diff --git a/packages/wallets/src/features/accounts/modules/components/Footer/Footer.scss b/packages/wallets/src/features/accounts/modules/components/Footer/Footer.scss
new file mode 100644
index 000000000000..dede91c56f3d
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/components/Footer/Footer.scss
@@ -0,0 +1,10 @@
+.wallets-accounts-module-footer {
+ width: 100%;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.8rem;
+
+ @include mobile-or-tablet-screen {
+ justify-content: center;
+ }
+}
diff --git a/packages/wallets/src/features/accounts/modules/components/Footer/Footer.tsx b/packages/wallets/src/features/accounts/modules/components/Footer/Footer.tsx
new file mode 100644
index 000000000000..8d62d97df6a9
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/components/Footer/Footer.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { useTranslations } from '@deriv-com/translations';
+import { Button, useDevice } from '@deriv-com/ui';
+import './Footer.scss';
+
+type TFooterProps = {
+ backText?: string;
+ disableBack?: boolean;
+ disableNext?: boolean;
+ nextText?: string;
+ onClickBack?: VoidFunction;
+ onClickNext?: VoidFunction;
+};
+
+const Footer: React.FC = ({
+ backText,
+ disableBack = false,
+ disableNext = false,
+ nextText,
+ onClickBack,
+ onClickNext,
+}) => {
+ const { isDesktop } = useDevice();
+ const { localize } = useTranslations();
+
+ return (
+
+ {onClickBack && (
+
+ {backText ?? localize('Back')}
+
+ )}
+ {onClickNext && (
+
+ {nextText ?? localize('Next')}
+
+ )}
+
+ );
+};
+
+export default Footer;
diff --git a/packages/wallets/src/features/accounts/modules/components/Footer/index.ts b/packages/wallets/src/features/accounts/modules/components/Footer/index.ts
new file mode 100644
index 000000000000..da94c293677a
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/components/Footer/index.ts
@@ -0,0 +1 @@
+export { default as Footer } from './Footer';
diff --git a/packages/wallets/src/features/accounts/modules/components/index.ts b/packages/wallets/src/features/accounts/modules/components/index.ts
new file mode 100644
index 000000000000..ddcc5a9cd184
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/components/index.ts
@@ -0,0 +1 @@
+export * from './Footer';
diff --git a/packages/wallets/src/features/accounts/modules/index.ts b/packages/wallets/src/features/accounts/modules/index.ts
new file mode 100644
index 000000000000..2445d2afb95f
--- /dev/null
+++ b/packages/wallets/src/features/accounts/modules/index.ts
@@ -0,0 +1,4 @@
+export * from './DocumentService';
+export * from './Poa';
+export * from './Poi';
+export * from './TaxInformation';
diff --git a/packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.tsx b/packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.tsx
deleted file mode 100644
index 416e92f9e404..000000000000
--- a/packages/wallets/src/features/accounts/screens/AddressSection/AddressSection.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react';
-import { useSettings, useStatesList } from '@deriv/api-v2';
-import { Dropdown } from '@deriv-com/ui';
-import { FlowTextField, useFlow } from '../../../../components';
-import { InlineMessage, WalletText } from '../../../../components/Base';
-import {
- addressFirstLineValidator,
- addressSecondLineValidator,
- cityValidator,
- postcodeValidator,
-} from '../../validations';
-import './AddressSection.scss';
-
-const AddressSection: React.FC = () => {
- const { setFormValues } = useFlow();
- const { data: getSettings } = useSettings();
- const country = getSettings?.country_code ?? '';
- const { data: statesList } = useStatesList(country);
-
- return (
-
-
-
-
-
- For faster verification, input the same address here as in your proof of address document (see
- section below)
-
-
-
-
-
-
-
-
- setFormValues('stateProvinceDropdownLine', selectedItem)}
- value={getSettings?.address_state ?? ''}
- />
-
-
-
-
- );
-};
-
-export default AddressSection;
diff --git a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/__tests__/CommonMistakesExamples.spec.tsx b/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/__tests__/CommonMistakesExamples.spec.tsx
deleted file mode 100644
index 8194f3756149..000000000000
--- a/packages/wallets/src/features/accounts/screens/CommonMistakesExamples/__tests__/CommonMistakesExamples.spec.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import CommonMistakesExamples from '../CommonMistakesExamples';
-
-jest.mock('../../../../../public/images/status-loss.svg', () => {
- const MockStatusLoss = () => ;
- MockStatusLoss.displayName = 'StatusLoss';
- return MockStatusLoss;
-});
-
-describe('CommonMistakesExamples', () => {
- it('renders the description and image', () => {
- const mockDescription = 'Test description';
- const mockImage = ;
-
- render( );
-
- expect(screen.getByText(mockDescription)).toBeInTheDocument();
- expect(screen.getByAltText('mock')).toBeInTheDocument();
- });
-
- it('renders the StatusLoss image', () => {
- const mockDescription = 'Test description';
- const mockImage = ;
-
- render( );
-
- expect(screen.getByAltText('StatusLoss')).toBeInTheDocument();
- });
-});
diff --git a/packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.tsx b/packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.tsx
deleted file mode 100644
index de2392a54227..000000000000
--- a/packages/wallets/src/features/accounts/screens/DocumentSubmission/DocumentSubmission.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-import { Dropzone, useFlow, WalletText } from '../../../../components';
-import useDevice from '../../../../hooks/useDevice';
-import Upload from '../../../../public/images/accounts/upload.svg';
-import { getExampleImagesConfig } from '../../constants';
-import { CommonMistakesExamples } from '../CommonMistakesExamples';
-import './DocumentSubmission.scss';
-
-const listItems = [
- 'Utility bill: electricity, water, gas, or landline phone bill.',
- 'Financial, legal, or government document: recent bank statement, affidavit, or government-issued letter.',
- 'Home rental agreement: valid and current agreement.',
-];
-
-const DocumentSubmission: React.FC = () => {
- const { isMobile } = useDevice();
- const { setFormValues } = useFlow();
-
- return (
-
-
-
Document submission
-
-
-
-
-
- We accept only these types of documents as proof of address. The document must be recent (issued
- within last 12 months) and include your name and address:
-
-
-
- {listItems.map(item => (
-
- {item}
-
- ))}
-
-
-
-
- Common mistakes
-
-
-
- {getExampleImagesConfig().map(config => (
- }
- key={`common-mistake-${config.description}`}
- />
- ))}
-
-
-
-
- Upload file
-
-
}
- maxSize={8388608}
- onFileChange={(file?: File) => setFormValues('poaDocument', file)}
- title='Drag and drop a file or click to browse your files.'
- titleType='bold'
- />
-
-
- Supported formats : JPEG, JPG, PNG, PDF, and GIF only
-
- Maximum size : 8MB
-
-
-
-
- );
-};
-
-export default DocumentSubmission;
diff --git a/packages/wallets/src/features/accounts/screens/DocumentSubmission/__tests__/DocumentSubmission.spec.tsx b/packages/wallets/src/features/accounts/screens/DocumentSubmission/__tests__/DocumentSubmission.spec.tsx
deleted file mode 100644
index 61ac71debe2a..000000000000
--- a/packages/wallets/src/features/accounts/screens/DocumentSubmission/__tests__/DocumentSubmission.spec.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react';
-import { render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { FlowProvider } from '../../../../../components';
-import useDevice from '../../../../../hooks/useDevice';
-import DocumentSubmission from '../DocumentSubmission';
-
-jest.mock('../../../../../hooks/useDevice', () => ({
- __esModule: true,
- default: jest.fn(() => ({
- ...jest.requireActual('../../../../../hooks/useDevice').default(),
- isMobile: false,
- })),
-}));
-
-const mockSetFormValues = jest.fn();
-jest.mock('../../../../../components/FlowProvider', () => ({
- ...jest.requireActual('../../../../../components/FlowProvider'),
- useFlow: jest.fn(() => ({
- setFormValues: mockSetFormValues,
- })),
-}));
-
-describe('DocumentSubmission', () => {
- beforeAll(() => {
- global.URL.createObjectURL = jest.fn();
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- it('should render document submission screen', () => {
- render(
- ,
- }}
- >
- {() => }
-
- );
- expect(screen.getByText('Document submission')).toBeInTheDocument();
- expect(
- screen.getByText(
- 'We accept only these types of documents as proof of address. The document must be recent (issued within last 12 months) and include your name and address:'
- )
- ).toBeInTheDocument();
- expect(screen.getByText('Utility bill: electricity, water, gas, or landline phone bill.')).toBeInTheDocument();
- expect(
- screen.getByText(
- 'Financial, legal, or government document: recent bank statement, affidavit, or government-issued letter.'
- )
- ).toBeInTheDocument();
- expect(screen.getByText('Home rental agreement: valid and current agreement.')).toBeInTheDocument();
- expect(screen.getByText('Upload file')).toBeInTheDocument();
- expect(
- screen.getByText('Remember, selfies, pictures of houses, or non-related images will be rejected.')
- ).toBeInTheDocument();
- expect(screen.getByText('Drag and drop a file or click to browse your files.')).toBeInTheDocument();
- expect(screen.getByText('Supported formats : JPEG, JPG, PNG, PDF, and GIF only')).toBeInTheDocument();
- expect(screen.getByText('Maximum size : 8MB')).toBeInTheDocument();
- expect(screen.getByText('Common mistakes')).toBeInTheDocument();
- expect(screen.getByText('Name in document doesn’t match your Deriv profile.')).toBeInTheDocument();
- expect(screen.getByText('Address in document doesn’t match address you entered above.')).toBeInTheDocument();
- expect(screen.getByText('Document issued more than 12-months ago.')).toBeInTheDocument();
- expect(screen.getByText('Blurry document. All information must be clear and visible.')).toBeInTheDocument();
- expect(screen.getByText('Cropped document. All information must be clear and visible.')).toBeInTheDocument();
- expect(screen.getByText('An envelope with your name and address.')).toBeInTheDocument();
- });
-
- it('should be able to upload document', async () => {
- render(
- ,
- }}
- >
- {() => }
-
- );
-
- const document = new File(['foo'], 'foo.jpeg', { type: 'image/jpeg' });
- const input: HTMLInputElement = screen.getByTestId('dt_dropzone-input');
-
- await waitFor(() => {
- userEvent.upload(input, document);
- });
-
- expect(input.files).toHaveLength(1);
- expect(mockSetFormValues).toBeCalledWith('poaDocument', document);
- });
-
- it('should show particular texts with the correct size in mobile view', () => {
- (useDevice as jest.Mock).mockImplementation(() => ({
- isMobile: true,
- }));
- render(
- ,
- }}
- >
- {() => }
-
- );
-
- expect(screen.getByText('Supported formats : JPEG, JPG, PNG, PDF, and GIF only')).toHaveClass(
- 'wallets-text__size--xs'
- );
- expect(screen.getByText('Maximum size : 8MB')).toHaveClass('wallets-text__size--xs');
- });
-});
diff --git a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.tsx
deleted file mode 100644
index 62c09f22ddaf..000000000000
--- a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/IDVDocumentUpload.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import React, { useMemo } from 'react';
-import * as Yup from 'yup';
-import { usePOI, useResidenceList, useSettings } from '@deriv/api-v2';
-import { Dropdown } from '@deriv-com/ui';
-import { FlowTextField, useFlow, WalletText } from '../../../../components';
-import { InlineMessage } from '../../../../components/Base';
-import useDevice from '../../../../hooks/useDevice';
-import { THooks } from '../../../../types';
-import { statusCodes } from '../../constants';
-import { requiredValidator } from '../../validations';
-import { VerifyDocumentDetails } from '../VerifyDocumentDetails';
-import './IDVDocumentUpload.scss';
-
-type TErrorMessageProps = Exclude;
-
-const statusMessage: Partial> = {
- expired: 'Your identity document has expired.',
- rejected: 'We were unable to verify the identity document with the details provided.',
-};
-
-const documentTypeToExampleMapper: Record = {
- drivers_license: 'B1234567',
- passport: 'G1234567',
- ssnit: 'C123456789012',
-};
-
-type TDocumentTypeItem = {
- pattern?: string;
- text: string;
- value: string;
-};
-
-const ErrorMessage: React.FC<{ status: TErrorMessageProps }> = ({ status }) => {
- const { isMobile } = useDevice();
-
- return (
-
- Your identity verification failed because:
-
-
- Let's try again. Choose another document and enter the corresponding details.
-
-
- );
-};
-
-const IDVDocumentUpload = () => {
- const { data: poiStatus } = usePOI();
- const { formValues, setFormValues } = useFlow();
- const { data: residenceList, isSuccess: isResidenceListSuccess } = useResidenceList();
- const { data: settings } = useSettings();
-
- const [documentsDropdownList, documentsMapper, textToValueMapper] = useMemo(() => {
- const documents: Record = {};
- const textToValueMapping: Record = {};
- const list: TDocumentTypeItem[] = [];
- if (isResidenceListSuccess) {
- const residence = residenceList.filter(residence => residence.value === settings.citizen)[0];
- if (residence) {
- const supportedDocuments = residence.identity?.services?.idv?.documents_supported || {};
- Object.keys(supportedDocuments).forEach(document => {
- const text = supportedDocuments[document].display_name || '';
- const value = document;
- documents[document] = {
- pattern: supportedDocuments[document].format,
- text,
- value,
- };
- list.push({
- text,
- value: document,
- });
- if (!(text in textToValueMapping)) textToValueMapping[text] = value;
- });
- }
- }
- return [list, documents, textToValueMapping];
- }, [isResidenceListSuccess, residenceList, settings.citizen]);
-
- const validationSchema = useMemo(() => {
- const documentTypeValue = formValues?.documentType;
- const document = documentsMapper[documentTypeValue];
-
- if (document && document.pattern) {
- let pattern;
- try {
- pattern = new RegExp(document.pattern);
- } catch (err) {
- const match = document.pattern.match(/(\(\?i\))/);
- if (match) {
- // Passport pattern has (?i) which is not supported in RegExp
- // Replace the (?i) flag with the 'i' flag
- const patternWithoutFlag = document.pattern.replace(/(\(\?i\))/, '');
- pattern = new RegExp(patternWithoutFlag, 'i');
- }
- }
-
- if (pattern)
- return Yup.string()
- .matches(
- pattern,
- `Please enter the correct format. ${
- documentTypeValue in documentTypeToExampleMapper
- ? `Example: ${documentTypeToExampleMapper[documentTypeValue]}`
- : ''
- }`
- )
- .required(`Please enter your ${document.text} number.`);
- }
-
- return requiredValidator;
- }, [documentsMapper, formValues?.documentType]);
-
- const status = poiStatus?.current.status;
-
- const negativeStatuses = status === statusCodes.expired || status === statusCodes.rejected;
-
- return (
-
-
- {negativeStatuses &&
}
-
- Identity verification
-
-
- {
- setFormValues('documentType', textToValueMapper[inputValue]);
- }}
- onSelect={selectedItem => {
- setFormValues('documentType', selectedItem);
- }}
- value={formValues?.documentType}
- variant='comboBox'
- />
-
-
-
- Details
-
-
-
-
- );
-};
-
-export default IDVDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/index.ts
deleted file mode 100644
index 7eacdc2d96fe..000000000000
--- a/packages/wallets/src/features/accounts/screens/IDVDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as IDVDocumentUpload } from './IDVDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.scss b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.scss
deleted file mode 100644
index 26ebd0e15724..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.wallets-manual-document-upload {
- display: flex;
- justify-content: center;
- width: 99.6rem;
- height: 60rem;
-
- @include mobile {
- width: 100%;
- height: 100%;
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.tsx
deleted file mode 100644
index 4891eb5ea6de..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/ManualDocumentUpload.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import { useFlow } from '../../../../components/FlowProvider';
-import { DocumentSelection } from './components/DocumentSelection';
-import {
- DrivingLicenseDocumentUpload,
- IdentityCardDocumentUpload,
- NIMCSlipDocumentUpload,
- PassportDocumentUpload,
-} from './components';
-import './ManualDocumentUpload.scss';
-
-const ManualDocumentUploadContent = () => {
- const { formValues, setFormValues } = useFlow();
-
- if (formValues.selectedManualDocument === 'passport') {
- return ;
- } else if (formValues.selectedManualDocument === 'driving-license') {
- return ;
- } else if (formValues.selectedManualDocument === 'identity-card') {
- return ;
- } else if (formValues.selectedManualDocument === 'nimc-slip') {
- return ;
- }
-
- return setFormValues('selectedManualDocument', doc)} />;
-};
-
-const ManualDocumentUpload = () => {
- return (
-
-
-
- );
-};
-
-export default ManualDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/__tests__/ManualDocumentUpload.spec.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/__tests__/ManualDocumentUpload.spec.tsx
deleted file mode 100644
index f227b3c3cfb3..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/__tests__/ManualDocumentUpload.spec.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { APIProvider } from '@deriv/api-v2';
-import { render, screen } from '@testing-library/react';
-import WalletsAuthProvider from '../../../../../AuthProvider';
-import { FlowProvider } from '../../../../../components';
-import ManualDocumentUpload from '../ManualDocumentUpload';
-
-describe(' ', () => {
- it('should set selected document', () => {
- const screens = {
- manualScreen: ,
- };
-
- render(
-
-
-
- {() => {
- return ;
- }}
-
-
-
- );
-
- expect(screen.getByText('Please upload one of the following documents:')).toBeInTheDocument();
- const passportCard = screen.getByTestId('dt_passport');
- expect(passportCard).toBeInTheDocument();
- passportCard.click();
- expect(screen.getByTestId('dt_passport-document-upload')).toBeInTheDocument();
- });
-});
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.tsx
deleted file mode 100644
index 2b7bb10e8d75..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/DocumentRuleHint.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React, { ReactNode } from 'react';
-import { WalletText } from '../../../../../../../components/Base';
-import './DocumentRuleHint.scss';
-
-type TProps = {
- description?: ReactNode;
- icon: ReactNode;
-};
-
-const DocumentRuleHint: React.FC = ({ description, icon }) => (
-
- {icon}
-
- {description}
-
-
-);
-
-export default DocumentRuleHint;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/index.ts
deleted file mode 100644
index 5f4ef30c07a1..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHint/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DocumentRuleHint } from './DocumentRuleHint';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.tsx
deleted file mode 100644
index 40f3a5938f8e..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/DocumentRuleHints.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import ClearPhoto from '../../../../../../public/images/accounts/clear-photo.svg';
-import ClockIcon from '../../../../../../public/images/accounts/clock-icon.svg';
-import ImageIcon from '../../../../../../public/images/accounts/image-icon.svg';
-import LessThanEightIcon from '../../../../../../public/images/accounts/less-than-eight-icon.svg';
-import { DocumentRuleHint } from './DocumentRuleHint';
-import './DocumentRuleHints.scss';
-
-type TProps = {
- docType: 'drivingLicense' | 'identityCard' | 'nimcSlip' | 'passport';
-};
-
-const DocumentRuleHints: React.FC = ({ docType }) => (
-
- } />
- } />
- } />
- {docType !== 'nimcSlip' && (
- } />
- )}
-
-);
-
-export default DocumentRuleHints;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/index.ts
deleted file mode 100644
index 36406d4c44b6..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentRuleHints/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DocumentRuleHints } from './DocumentRuleHints';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.tsx
deleted file mode 100644
index f12aa3c71d86..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelection/DocumentSelection.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { useSettings } from '@deriv/api-v2';
-import { WalletText } from '../../../../../../components/Base';
-import { documentTypes } from '../../constants';
-import { DocumentSelectionCard } from '../DocumentSelectionCard';
-import './DocumentSelection.scss';
-
-type TProps = {
- setSelectedDocument: (document: string) => void;
-};
-
-const DocumentSelection: React.FC = ({ setSelectedDocument }) => {
- const { data } = useSettings();
-
- return (
-
-
- Please upload one of the following documents:
- {documentTypes.map(({ countries, description, icon, title, value }) => {
- if (countries && !countries.includes(data?.country_code ?? '')) {
- return null;
- }
- return (
-
- );
- })}
-
-
- );
-};
-
-export default DocumentSelection;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/DocumentSelectionCard.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/DocumentSelectionCard.tsx
deleted file mode 100644
index 3cc97e4776a9..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DocumentSelectionCard/DocumentSelectionCard.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import { WalletText } from '../../../../../../components/Base';
-import useDevice from '../../../../../../hooks/useDevice';
-import RightArrow from '../../../../../../public/images/navigation-chevron-right.svg';
-import { TDocumentType } from '../../constants';
-import './DocumentSelectionCard.scss';
-
-type TProps = TDocumentType & {
- onClick: (document: string) => void;
-};
-
-const DocumentSelectionCard: React.FC = ({ description, icon: Icon, onClick, title, value }) => {
- const { isMobile } = useDevice();
- return (
- onClick(value)}>
-
-
-
-
- {title}
-
- {description}
-
-
-
-
- );
-};
-
-export default DocumentSelectionCard;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.scss b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.scss
deleted file mode 100644
index 2acd89b3eb9c..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-.wallets-driving-license-document-upload {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding: 2.4rem;
- gap: 2.4rem;
- background: var(--light-8-primary-background, #fff);
-
- @include mobile {
- width: 100%;
- gap: 1.6rem;
- }
-
- &__input-group {
- width: 100%;
- }
-
- &__document-upload {
- width: 100%;
- display: grid;
- gap: 1.7rem;
-
- @include mobile {
- gap: 1.6rem;
- }
- }
-
- &__input-group,
- &__dropzone {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 2.4rem;
-
- @include mobile {
- grid-template-columns: unset;
- gap: 1.6rem;
- }
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.tsx
deleted file mode 100644
index 462878c0ac2e..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/DrivingLicenseDocumentUpload.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import { Divider } from '@deriv-com/ui';
-import { DatePicker, Dropzone, FlowTextField, useFlow, WalletText } from '../../../../../../components';
-import DrivingLicenseCardBack from '../../../../../../public/images/accounts/document-back.svg';
-import DrivingLicenseCardFront from '../../../../../../public/images/accounts/driving-license-front.svg';
-import { documentRequiredValidator, expiryDateValidator } from '../../../../validations';
-import { DocumentRuleHints } from '../DocumentRuleHints';
-import './DrivingLicenseDocumentUpload.scss';
-
-const DrivingLicenseDocumentUpload = () => {
- const { formValues, setFormValues } = useFlow();
-
- const handleDateChange = (formattedDate: string | null) => {
- setFormValues('drivingLicenseExpiryDate', formattedDate);
- };
-
- return (
-
-
First, enter your Driving licence number and the expiry date.
-
-
-
-
-
-
-
Next, upload the front and back of your driving licence.
-
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('drivingLicenseCardFront', file)}
- />
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('drivingLicenseCardBack', file)}
- />
-
-
-
-
- );
-};
-
-export default DrivingLicenseDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/index.ts
deleted file mode 100644
index bafc7fbc34ea..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/DrivingLicenseDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DrivingLicenseDocumentUpload } from './DrivingLicenseDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.scss b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.scss
deleted file mode 100644
index a2f0fe52c1bb..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-.wallets-identity-card-document-upload {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding: 2.4rem;
- gap: 2.4rem;
- background: var(--light-8-primary-background, #fff);
-
- @include mobile {
- width: 100%;
- gap: 1.6rem;
- }
-
- &__input-group {
- width: 100%;
- }
-
- &__document-upload {
- width: 100%;
- display: grid;
- gap: 1.7rem;
-
- @include mobile {
- gap: 1.6rem;
- }
- }
-
- &__input-group,
- &__dropzone {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 2.4rem;
-
- @include mobile {
- grid-template-columns: unset;
- gap: 1.6rem;
- }
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.tsx
deleted file mode 100644
index c8f1a68ab65b..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/IdentityCardDocumentUpload.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import { Divider } from '@deriv-com/ui';
-import { DatePicker, Dropzone, FlowTextField, useFlow, WalletText } from '../../../../../../components';
-import IdentityCardBack from '../../../../../../public/images/accounts/document-back.svg';
-import IdentityCardFront from '../../../../../../public/images/accounts/identity-card-front.svg';
-import { documentRequiredValidator, expiryDateValidator } from '../../../../validations';
-import { DocumentRuleHints } from '../DocumentRuleHints';
-import './IdentityCardDocumentUpload.scss';
-
-const IdentityCardDocumentUpload = () => {
- const { formValues, setFormValues } = useFlow();
-
- const handleDateChange = (formattedDate: string | null) => {
- setFormValues('identityCardExpiryDate', formattedDate);
- };
- return (
-
-
First, enter your Identity card number and the expiry date.
-
-
-
-
-
-
-
Next, upload the front and back of your identity card.
-
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('identityCardFront', file)}
- />
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('identityCardBack', file)}
- />
-
-
-
-
- );
-};
-
-export default IdentityCardDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/index.ts
deleted file mode 100644
index e87b4691e064..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/IdentityCardDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as IdentityCardDocumentUpload } from './IdentityCardDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.tsx
deleted file mode 100644
index 0db7caeb5110..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/NIMCSlipDocumentUpload.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react';
-import { Divider } from '@deriv-com/ui';
-import { Dropzone, FlowTextField, useFlow, WalletText } from '../../../../../../components';
-import NIMCSlipFront from '../../../../../../public/images/accounts/nimc-slip-front.svg';
-import ProofOfAgeIcon from '../../../../../../public/images/accounts/proof-of-age.svg';
-import { documentRequiredValidator } from '../../../../validations';
-import { DocumentRuleHints } from '../DocumentRuleHints';
-import './NIMCSlipDocumentUpload.scss';
-
-const NIMCSlipDocumentUpload = () => {
- const { formValues, setFormValues } = useFlow();
-
- return (
-
-
First, enter your NIMC slip number.
-
-
-
-
Next, upload both of the following documents.
-
-
- }
- maxSize={8388608}
- onFileChange={(file?: File) => setFormValues('nimcCardFront', file)}
- />
-
-
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('nimcCardBack', file)}
- />
-
-
-
-
-
- );
-};
-
-export default NIMCSlipDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/index.ts
deleted file mode 100644
index cbe26891d36d..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/NIMCSlipDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as NIMCSlipDocumentUpload } from './NIMCSlipDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.scss b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.scss
deleted file mode 100644
index fa2f46b32eb6..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-.wallets-passport-document-upload {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding: 2.4rem;
- gap: 2.4rem;
- background: var(--light-8-primary-background, #fff);
-
- @include mobile {
- width: 100%;
- gap: 1.6rem;
- }
-
- &__document-upload {
- width: 100%;
- display: grid;
- gap: 1.7rem;
-
- @include mobile {
- gap: 1.6rem;
- }
- }
-
- &__input-group {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 2.4rem;
- width: 100%;
-
- @include mobile {
- grid-template-columns: unset;
- gap: 1.6rem;
- }
- }
-
- & .wallets-dropzone__container {
- min-height: 18.6rem;
-
- @include mobile {
- min-height: 25rem;
- }
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.tsx
deleted file mode 100644
index 2747b2e70225..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/PassportDocumentUpload.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import { Divider } from '@deriv-com/ui';
-import { DatePicker, Dropzone, FlowTextField, useFlow, WalletText } from '../../../../../../components';
-import PassportPlaceholder from '../../../../../../public/images/accounts/passport-placeholder.svg';
-import { documentRequiredValidator, expiryDateValidator } from '../../../../validations';
-import { DocumentRuleHints } from '../DocumentRuleHints';
-import './PassportDocumentUpload.scss';
-
-const PassportDocumentUpload = () => {
- const { formValues, setFormValues } = useFlow();
-
- const handleDateChange = (formattedDate: string | null) => {
- setFormValues('passportExpiryDate', formattedDate);
- };
-
- return (
-
-
First, enter your Passport number and the expiry date.
-
-
-
-
-
-
- Next, upload the page of your passport that contains your photo.
- }
- maxSize={8388608}
- noClick
- onFileChange={(file?: File) => setFormValues('passportCard', file)}
- />
-
-
-
- );
-};
-
-export default PassportDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/index.ts
deleted file mode 100644
index c51975cc4c07..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/PassportDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as PassportDocumentUpload } from './PassportDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts
deleted file mode 100644
index 4335b0f70bbd..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from './DrivingLicenseDocumentUpload';
-export * from './IdentityCardDocumentUpload';
-export * from './NIMCSlipDocumentUpload';
-export * from './PassportDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/constants.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/constants.tsx
deleted file mode 100644
index a8f92896b74d..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/constants.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { ComponentType, SVGAttributes } from 'react';
-import DrivingLicenseIcon from '../../../../public/images/accounts/driving-license.svg';
-import IdentityCardIcon from '../../../../public/images/accounts/identity-card.svg';
-import NIMCSlipIcon from '../../../../public/images/accounts/nimc-slip.svg';
-import PassportIcon from '../../../../public/images/accounts/passport.svg';
-
-export type TDocumentType = {
- countries?: string[];
- description: string;
- icon: ComponentType>;
- title: string;
- value: string;
-};
-
-export const documentTypes: TDocumentType[] = [
- {
- description: 'Upload the page that contains your photo.',
- icon: PassportIcon,
- title: 'Passport',
- value: 'passport',
- },
- {
- description: 'Upload the front and back of your driving licence.',
- icon: DrivingLicenseIcon,
- title: 'Driving licence',
- value: 'driving-license',
- },
- {
- description: 'Upload the front and back of your identity card.',
- icon: IdentityCardIcon,
- title: 'Identity card',
- value: 'identity-card',
- },
- {
- countries: ['NG'],
- description: 'Upload the front and back of your identity card.',
- icon: NIMCSlipIcon,
- title: 'NIMC slip and proof of age',
- value: 'nimc-slip',
- },
-];
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts
deleted file mode 100644
index 864046a81b14..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as useHandleManualDocumentUpload } from './useHandleManualDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx
deleted file mode 100644
index 682377201c75..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { useCallback } from 'react';
-import { FormikValues } from 'formik';
-import { useDocumentUpload, useSettings } from '@deriv/api-v2';
-
-const useHandleManualDocumentUpload = () => {
- const { data: settings } = useSettings();
- const { upload, ...rest } = useDocumentUpload();
-
- const uploadDocument = useCallback(
- async (formValues: FormikValues) => {
- if (formValues.selectedManualDocument === 'passport') {
- await upload({
- document_id: formValues.passportNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'passport',
- expiration_date: formValues.passportExpiryDate,
- file: formValues.passportCard,
- });
- } else if (formValues.selectedManualDocument === 'identity-card') {
- await upload({
- document_id: formValues.identityCardNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'national_identity_card',
- expiration_date: formValues.identityCardExpiryDate,
- file: formValues.identityCardFront,
- page_type: 'front',
- });
- await upload({
- document_id: formValues.identityCardNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'national_identity_card',
- expiration_date: formValues.identityCardExpiryDate,
- file: formValues.identityCardBack,
- page_type: 'back',
- });
- } else if (formValues.selectedManualDocument === 'driving-license') {
- await upload({
- document_id: formValues.drivingLicenceNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'driving_licence',
- expiration_date: formValues.drivingLicenseExpiryDate,
- file: formValues.drivingLicenseCardFront,
- page_type: 'front',
- });
- await upload({
- document_id: formValues.drivingLicenceNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'driving_licence',
- expiration_date: formValues.drivingLicenseExpiryDate,
- file: formValues.drivingLicenseCardBack,
- page_type: 'back',
- });
- } else if (formValues.selectedManualDocument === 'nimc-slip') {
- await upload({
- document_id: formValues.nimcNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'nimc_slip',
- file: formValues.nimcCardFront,
- page_type: 'front',
- });
- await upload({
- document_id: formValues.nimcNumber,
- document_issuing_country: settings?.country_code ?? undefined,
- document_type: 'nimc_slip',
- file: formValues.nimcCardBack,
- page_type: 'back',
- });
- }
- },
- [settings, upload]
- );
-
- return { uploadDocument, ...rest };
-};
-
-export default useHandleManualDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts
deleted file mode 100644
index 2f34fe22f8c1..000000000000
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './hooks';
-export { default as ManualDocumentUpload } from './ManualDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx b/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx
deleted file mode 100644
index 62b9d01b7ed3..000000000000
--- a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import React, { useEffect, useMemo } from 'react';
-import * as Yup from 'yup';
-import { useResidenceList, useSettings } from '@deriv/api-v2';
-import { Dropdown, Loader } from '@deriv-com/ui';
-import { FlowTextField, InlineMessage, useFlow, WalletText } from '../../../../components';
-import { accountOpeningReasonList } from './constants';
-import './PersonalDetails.scss';
-
-const PersonalDetails = () => {
- const { data: residenceList, isLoading, isSuccess: isResidenceListSuccess } = useResidenceList();
- const { formValues, setFormValues } = useFlow();
- const { data: getSettings } = useSettings();
-
- const countryCodeToPatternMapper = useMemo(() => {
- const countryCodeToPatternMapping: Record = {};
-
- if (isResidenceListSuccess) {
- residenceList.forEach(residence => {
- if (residence.value && !(residence.value in countryCodeToPatternMapping)) {
- countryCodeToPatternMapping[residence.value] = residence?.tin_format?.[0] ?? '';
- }
- });
- }
- return countryCodeToPatternMapping;
- }, [isResidenceListSuccess, residenceList]);
-
- const tinValidator = useMemo(() => {
- const patternStr = countryCodeToPatternMapper[formValues?.taxResidence];
- try {
- if (patternStr) {
- Yup.string()
- .required('Please fill in Tax identification number')
- .matches(new RegExp(patternStr), 'The format is incorrect.')
- .validateSync(formValues?.taxIdentificationNumber);
- } else {
- Yup.string()
- .required('Please fill in Tax identification number')
- .validateSync(formValues?.taxIdentificationNumber);
- }
- } catch (err) {
- return (err as Yup.ValidationError).message;
- }
- }, [countryCodeToPatternMapper, formValues?.taxIdentificationNumber, formValues?.taxResidence]);
-
- useEffect(() => {
- if (getSettings && isResidenceListSuccess) {
- setFormValues('citizenship', getSettings.citizen);
- setFormValues('placeOfBirth', getSettings.place_of_birth);
- setFormValues('taxResidence', getSettings.tax_residence);
- setFormValues('accountOpeningReason', getSettings.account_opening_reason);
- }
- }, [getSettings, setFormValues, isResidenceListSuccess]);
-
- return (
-
- {isLoading &&
}
- {!isLoading && (
- <>
-
-
- Complete your personal details
-
-
- Any information you provide is confidential and will be used for verification purposes only.
-
-
-
-
-
- Need help with tax info? Let us know via{' '}
- window.LC_API.open_chat_window()}
- >
- live chat
-
- .
-
-
-
-
-
- ({
- text: residence.text,
- value: residence.value ?? '',
- }))}
- listHeight='sm'
- name='wallets-personal-details__dropdown-citizenship'
- onSelect={selectedItem => setFormValues('citizenship', selectedItem)}
- value={formValues?.citizenship ?? getSettings?.citizen}
- variant='comboBox'
- />
-
-
- ({
- text: residence.text,
- value: residence.value ?? '',
- }))}
- listHeight='sm'
- name='wallets-personal-details__dropdown-pob'
- onSelect={selectedItem => setFormValues('placeOfBirth', selectedItem)}
- value={getSettings?.place_of_birth ?? ''}
- variant='comboBox'
- />
-
-
- ({
- text: residence.text,
- value: residence.value ?? '',
- }))}
- listHeight='sm'
- name='wallets-personal-details__dropdown-tax-residence'
- onSearch={inputValue => {
- residenceList.forEach(residence => {
- if (residence.text?.toLowerCase() === inputValue.toLowerCase()) {
- setFormValues('taxResidence', residence.value);
- }
- });
- }}
- onSelect={selectedItem => {
- setFormValues('taxResidence', selectedItem);
- }}
- value={formValues?.taxResidence ?? getSettings?.tax_residence}
- variant='comboBox'
- />
-
-
-
- setFormValues('accountOpeningReason', selectedItem)}
- value={formValues?.accountOpeningReason ?? getSettings?.account_opening_reason}
- variant='comboBox'
- />
-
-
- >
- )}
-
- );
-};
-
-export default PersonalDetails;
diff --git a/packages/wallets/src/features/accounts/screens/PersonalDetails/constants.tsx b/packages/wallets/src/features/accounts/screens/PersonalDetails/constants.tsx
deleted file mode 100644
index d7e4d93c072a..000000000000
--- a/packages/wallets/src/features/accounts/screens/PersonalDetails/constants.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-export const accountOpeningReasonList = [
- {
- text: 'Hedging',
- value: 'Hedging',
- },
- {
- text: 'Income Earning',
- value: 'Income Earning',
- },
- {
- text: 'Speculative',
- value: 'Speculative',
- },
- {
- text: 'Peer-to-peer exchange',
- value: 'Peer-to-peer exchange',
- },
-];
diff --git a/packages/wallets/src/features/accounts/screens/PersonalDetails/index.ts b/packages/wallets/src/features/accounts/screens/PersonalDetails/index.ts
deleted file mode 100644
index 6815914afb9c..000000000000
--- a/packages/wallets/src/features/accounts/screens/PersonalDetails/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as PersonalDetails } from './PersonalDetails';
diff --git a/packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.tsx b/packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.tsx
deleted file mode 100644
index a6d3e3abbe00..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoaScreen/PoaScreen.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import { AddressSection } from '../AddressSection';
-import { DocumentSubmission } from '../DocumentSubmission';
-import './PoaScreen.scss';
-
-const PoaScreen: React.FC = () => (
-
-);
-
-export default PoaScreen;
diff --git a/packages/wallets/src/features/accounts/screens/PoaScreen/index.ts b/packages/wallets/src/features/accounts/screens/PoaScreen/index.ts
deleted file mode 100644
index 642e4b9e4f90..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoaScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as PoaScreen } from './PoaScreen';
diff --git a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.scss b/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.scss
deleted file mode 100644
index 543ed6228426..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.wallets-poi-poa-submitted {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 99.6rem;
- height: 60rem;
-
- @include mobile {
- width: 100%;
- height: 100%;
- padding: 0 1.6rem;
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.tsx b/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.tsx
deleted file mode 100644
index f9524e404342..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/PoiPoaDocsSubmitted.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { useEffect } from 'react';
-import { useInvalidateQuery } from '@deriv/api-v2';
-import { DerivLightWaitingPoiIcon } from '@deriv/quill-icons';
-import { useFlow, WalletButton, WalletsActionScreen } from '../../../../components';
-import { useModal } from '../../../../components/ModalProvider';
-import './PoiPoaDocsSubmitted.scss';
-
-const PoiPoaDocsSubmitted = () => {
- const invalidate = useInvalidateQuery();
- const { hide } = useModal();
- const { formValues } = useFlow();
-
- // need invalidate queries in order to update status badge of CFD account
- useEffect(() => {
- return () => {
- invalidate('get_account_status');
- invalidate('mt5_login_list');
- };
- }, [invalidate]);
-
- return (
-
- }
- renderButtons={() => (
-
- Ok
-
- )}
- title='Your documents were submitted successfully'
- />
-
- );
-};
-
-export default PoiPoaDocsSubmitted;
diff --git a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/index.ts b/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/index.ts
deleted file mode 100644
index 4c50ddf63d3d..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiPoaDocsSubmitted/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as PoiPoaDocsSubmitted } from './PoiPoaDocsSubmitted';
diff --git a/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.scss b/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.scss
deleted file mode 100644
index 0cd8abcdf374..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.wallets-poi-upload-error {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 99.6rem;
- height: 60rem;
-
- @include mobile {
- width: 100%;
- height: 100%;
- padding: auto 1.6rem;
- }
-}
diff --git a/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.tsx b/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.tsx
deleted file mode 100644
index ed26a989f236..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiUploadError/PoiUploadError.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react';
-import { DerivLightDeclinedPoiIcon } from '@deriv/quill-icons';
-import { WalletButton } from '../../../../components/Base';
-import { WalletsActionScreen } from '../../../../components/WalletsActionScreen';
-import { ErrorCode } from '../../constants';
-import './PoiUploadError.scss';
-import { useFlow } from '../../../../components/FlowProvider';
-
-type PoiUploadErrorProps = {
- errorCode: keyof typeof ErrorCode;
-};
-
-const errorCodeToDescriptionMapper: Record = {
- DuplicateUpload: 'It seems you’ve submitted this document before. Upload a new document.',
-};
-
-const PoiUploadError = ({ errorCode }: PoiUploadErrorProps) => {
- const { formValues, setFormValues, switchScreen } = useFlow();
-
- // clears the form values to navigate back to document selection
- const switchBackToDocumentSelection = () => {
- if (formValues.selectedManualDocument === 'passport') {
- setFormValues('passportNumber', '');
- setFormValues('passportExpiryDate', '');
- setFormValues('passportCard', '');
- } else if (formValues.selectedManualDocument === 'driving-license') {
- setFormValues('drivingLicenseNumber', '');
- setFormValues('drivingLicenseExpiryDate', '');
- setFormValues('drivingLicenseCardFront', '');
- setFormValues('drivingLicenseCardBack', '');
- } else if (formValues.selectedManualDocument === 'identity-card') {
- setFormValues('identityCardNumber', '');
- setFormValues('identityCardExpiryDate', '');
- setFormValues('identityCardFront', '');
- setFormValues('identityCardBack', '');
- } else if (formValues.selectedManualDocument === 'nimc-slip') {
- setFormValues('nimcNumber', '');
- setFormValues('nimcCardFront', '');
- setFormValues('nimcCardBack', '');
- }
-
- setFormValues('selectedManualDocument', '');
- switchScreen('manualScreen');
- };
-
- return (
-
- }
- renderButtons={() => (
-
- Try again
-
- )}
- title='Proof of identity documents upload failed'
- />
-
- );
-};
-
-export default PoiUploadError;
diff --git a/packages/wallets/src/features/accounts/screens/PoiUploadError/index.ts b/packages/wallets/src/features/accounts/screens/PoiUploadError/index.ts
deleted file mode 100644
index ea00391cc852..000000000000
--- a/packages/wallets/src/features/accounts/screens/PoiUploadError/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as PoiUploadError } from './PoiUploadError';
diff --git a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx
deleted file mode 100644
index 846bef3c597c..000000000000
--- a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import { Dropzone, useFlow, WalletText } from '../../../../components';
-import useDevice from '../../../../hooks/useDevice';
-import SelfieIcon from '../../../../public/images/accounts/selfie-icon.svg';
-import './SelfieDocumentUpload.scss';
-
-const SelfieDocumentUpload = () => {
- const { isDesktop } = useDevice();
- const { formValues, setFormValues } = useFlow();
-
- return (
-
- Upload your selfie
- }
- noClick
- onFileChange={(file?: File) => setFormValues('selfie', file)}
- />
-
- Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and your face
- is within the frame.
-
-
- );
-};
-
-export default SelfieDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/index.ts
deleted file mode 100644
index dc59823cc14f..000000000000
--- a/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as SelfieDocumentUpload } from './SelfieDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.tsx b/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.tsx
deleted file mode 100644
index 35d1c2aa5ef1..000000000000
--- a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/VerifyDocumentDetails.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useEffect } from 'react';
-import classNames from 'classnames';
-import { Field, useFormikContext } from 'formik';
-import moment from 'moment';
-import { useSettings } from '@deriv/api-v2';
-import { DerivLightNameDobPoiIcon } from '@deriv/quill-icons';
-import { DatePicker, FlowTextField, InlineMessage, useFlow, WalletText } from '../../../../components';
-import unixToDateString from '../../../../utils/utils';
-import { dateOfBirthValidator, firstNameValidator, lastNameValidator } from '../../validations';
-import './VerifyDocumentDetails.scss';
-
-const VerifyDocumentDetails = () => {
- const { data: getSettings, update } = useSettings();
- const { currentScreenId, formValues, setFormValues } = useFlow();
- const { isValid, validateForm } = useFormikContext();
-
- const isOnfido = currentScreenId === 'onfidoScreen';
-
- const dateOfBirth = getSettings?.date_of_birth ?? 0;
- const formattedDateOfBirth = new Date(dateOfBirth * 1000);
- const firstName = getSettings?.first_name;
- const lastName = getSettings?.last_name;
-
- const isFormDirty =
- formValues.firstName !== getSettings.first_name ||
- formValues.lastName !== getSettings.last_name ||
- formValues.dateOfBirth !== unixToDateString(new Date(dateOfBirth * 1000));
-
- useEffect(() => {
- setFormValues('firstName', getSettings?.first_name);
- setFormValues('lastName', getSettings?.last_name);
- validateForm();
- }, [getSettings?.first_name, getSettings?.last_name, setFormValues, validateForm]);
-
- const handleDateChange = (formattedDate: string | null) => {
- setFormValues('dateOfBirth', formattedDate);
- };
-
- const handleTNCChecked = (event: React.ChangeEvent) => {
- if (event.target.checked && isFormDirty && isOnfido && isValid) {
- update({
- date_of_birth: formValues.dateOfBirth,
- first_name: formValues.firstName,
- last_name: formValues.lastName,
- });
- }
- };
-
- if (formValues.verifiedDocumentDetails && isOnfido)
- return (
-
- );
-
- return (
-
-
-
- To avoid delays, enter your name and date of birth exactly as it
- appears on your identity document.
-
-
-
-
-
-
-
-
-
-
- Example
-
-
-
-
-
-
-
-
- I confirm that the name and date of birth above match my chosen identity document
-
-
-
-
- );
-};
-
-export default VerifyDocumentDetails;
diff --git a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/__tests__/VerifyDocumentDetails.spec.tsx b/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/__tests__/VerifyDocumentDetails.spec.tsx
deleted file mode 100644
index 25cd9b7733eb..000000000000
--- a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/__tests__/VerifyDocumentDetails.spec.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import { useSettings } from '@deriv/api-v2';
-import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
-import { FlowProvider, FlowTextField } from '../../../../../components';
-import VerifyDocumentDetails from '../VerifyDocumentDetails';
-
-jest.mock('@deriv/api-v2', () => ({
- useSettings: jest.fn(),
-}));
-
-describe('IDVDocumentUploadDetails', () => {
- beforeEach(() => {
- (useSettings as jest.Mock).mockReturnValue({
- data: {
- date_of_birth: 0,
- first_name: 'John',
- last_name: 'Doe',
- },
- update: jest.fn(),
- });
- });
-
- test('should render component with default values', async () => {
- await act(async () => {
- render(
- ,
- }}
- >
- {() => {
- return ;
- }}
-
- );
- });
-
- expect(screen.getByLabelText('First name*')).toHaveValue('John');
- expect(screen.getByLabelText('Last name*')).toHaveValue('Doe');
- expect(screen.getByLabelText('Date of birth*')).toHaveValue('01-01-1970');
- expect(
- screen.getByLabelText(/I confirm that the name and date of birth above match my chosen identity document/)
- ).toBeInTheDocument();
- });
-
- test('should render component with date of birth if existing in getSettings', async () => {
- (useSettings as jest.Mock).mockReturnValue({
- data: {
- date_of_birth: moment().subtract(25, 'years').unix(),
- first_name: 'John',
- last_name: 'Doe',
- },
- });
-
- await act(async () => {
- render(
- ,
- }}
- >
- {() => {
- return ;
- }}
-
- );
- });
-
- expect(screen.getByLabelText('First name*')).toHaveValue('John');
- expect(screen.getByLabelText('Last name*')).toHaveValue('Doe');
- expect(screen.getByLabelText('Date of birth*')).toHaveValue(
- moment().subtract(25, 'years').format('DD-MM-YYYY')
- );
- expect(
- screen.getByLabelText(/I confirm that the name and date of birth above match my chosen identity document/)
- ).toBeInTheDocument();
- });
-
- test('check if set_settings call is not made if the form is not edited', async () => {
- const mockUpdate = jest.fn();
- (useSettings as jest.Mock).mockReturnValue({
- data: {
- date_of_birth: 0,
- first_name: 'John',
- last_name: 'Doe',
- },
- update: mockUpdate,
- });
-
- await act(async () => {
- render(
- ,
- }}
- >
- {() => {
- return ;
- }}
-
- );
- });
-
- await act(async () => {
- fireEvent.click(
- screen.getByLabelText(
- /I confirm that the name and date of birth above match my chosen identity document/
- )
- );
- });
-
- await waitFor(() => {
- expect(screen.getByTestId('dt_wallets_verify_document_details__placeholder')).toBeInTheDocument();
- expect(mockUpdate).not.toBeCalledWith({
- date_of_birth: '1970-01-01',
- first_name: 'John',
- last_name: 'Doe',
- });
- });
- });
-
- test('check if set_settings call made with the correct data is form is edited', async () => {
- const mockUpdate = jest.fn();
- (useSettings as jest.Mock).mockReturnValue({
- data: {
- date_of_birth: 0,
- first_name: 'John',
- last_name: 'Doe',
- },
- update: mockUpdate,
- });
-
- await act(async () => {
- render(
- ,
- }}
- >
- {() => {
- return ;
- }}
-
- );
- });
-
- act(() => {
- fireEvent.change(screen.getByPlaceholderText('First name*'), { target: { value: 'Bill' } });
- });
-
- act(() => {
- fireEvent.click(
- screen.getByLabelText(
- /I confirm that the name and date of birth above match my chosen identity document/
- )
- );
- });
-
- await waitFor(() => {
- expect(screen.getByTestId('dt_wallets_verify_document_details__placeholder')).toBeInTheDocument();
- expect(mockUpdate).toBeCalledWith({
- date_of_birth: '1970-01-01',
- first_name: 'Bill',
- last_name: 'Doe',
- });
- });
- });
-});
diff --git a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/index.ts b/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/index.ts
deleted file mode 100644
index 70c307547b14..000000000000
--- a/packages/wallets/src/features/accounts/screens/VerifyDocumentDetails/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as VerifyDocumentDetails } from './VerifyDocumentDetails';
diff --git a/packages/wallets/src/features/accounts/screens/index.ts b/packages/wallets/src/features/accounts/screens/index.ts
deleted file mode 100644
index 1c70ad6063d2..000000000000
--- a/packages/wallets/src/features/accounts/screens/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export * from './AddressSection';
-export * from './CommonMistakesExamples';
-export * from './DocumentSubmission';
-export * from './IDVDocumentUpload';
-export * from './ManualDocumentUpload';
-export * from './PersonalDetails';
-export * from './PoaScreen';
-export * from './PoiPoaDocsSubmitted';
-export * from './PoiUploadError';
-export * from './SelfieDocumentUpload';
-export * from './VerifyDocumentDetails';
diff --git a/packages/wallets/src/features/accounts/validations.ts b/packages/wallets/src/features/accounts/validations.ts
deleted file mode 100644
index 6007181c47bb..000000000000
--- a/packages/wallets/src/features/accounts/validations.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as Yup from 'yup';
-
-export const drivingLicenseValidator = Yup.string()
- .matches(/^[A-Z]\d{7}$/, 'Please enter the correct format. Example: B1234567')
- .max(8)
- .required('Please enter your Driver License number. Example: B1234567');
-
-export const passportValidator = Yup.string()
- .matches(/^[A-Z]\d{7}$/, 'Please enter the correct format. Example: G1234567')
- .max(8)
- .required('Please enter your Passport number. Example: G1234567');
-
-export const documentRequiredValidator = (documentType: string) => Yup.string().required(`${documentType} is required`);
-
-export const ssnitValidator = Yup.string()
- .matches(/^[A-Z]\d{12}$/, 'Please enter the correct format. Example: C123456789012')
- .max(13)
- .required('Please enter your SSNIT number. Example: C123456789012');
-
-export const nimcSlipValidator = Yup.string().matches(
- /^\d{11}$/,
- 'Please enter your document number. Example: 12345678901'
-);
-
-export const requiredValidator = Yup.string().required('This field is required');
-
-export const dateOfBirthValidator = Yup.date().required('Please enter your date of birth');
-
-export const expiryDateValidator = Yup.date()
- .min(new Date(), 'Expiry date cannot be today date or in the past')
- .required('Expiry date is required');
-
-export const firstNameValidator = Yup.string()
- .required('This field is required')
- .matches(/^[a-zA-Z\s\-.'']+$/, 'Letters, spaces, periods, hyphens, apostrophes only.')
- .min(1, 'Enter no more than 50 characters.')
- .max(50, 'Enter no more than 50 characters.');
-
-export const lastNameValidator = Yup.string()
- .required('This field is required')
- .matches(/^[a-zA-Z\s\-.'']+$/, 'Letters, spaces, periods, hyphens, apostrophes only.')
- .min(1, 'Enter no more than 50 characters.')
- .max(50, 'Enter no more than 50 characters.');
-
-export const addressFirstLineValidator = Yup.string()
- .trim()
- .required('First line of address is required.')
- .max(70, 'Should be less than 70.')
- .matches(
- /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u,
- "Use only the following special characters: . , ' : ; ( ) ° @ # / -'"
- );
-
-export const addressSecondLineValidator = Yup.string()
- .trim()
- .max(70, 'Should be less than 70.')
- .matches(
- /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u,
- "Use only the following special characters: . , ' : ; ( ) ° @ # / -'"
- );
-
-export const cityValidator = Yup.string()
- .required('Town/City is required.')
- .max(70, 'Should be less than 70.')
- .matches(/^[a-zA-Z\s\-.'']+$/, 'Only letters, space, hyphen, period, and apostrophe are allowed.');
-
-export const postcodeValidator = Yup.string()
- .max(20, 'Please enter a Postal/ZIP code under 20 characters.')
- .matches(/^[A-Za-z0-9][A-Za-z0-9\s-]*$/, 'Only letters, numbers, space, and hyphen are allowed.');
diff --git a/packages/wallets/src/features/cashier/components/WalletActionModal/WalletActionModal.tsx b/packages/wallets/src/features/cashier/components/WalletActionModal/WalletActionModal.tsx
index 184bb9681b70..edf8a50bb9c9 100644
--- a/packages/wallets/src/features/cashier/components/WalletActionModal/WalletActionModal.tsx
+++ b/packages/wallets/src/features/cashier/components/WalletActionModal/WalletActionModal.tsx
@@ -10,7 +10,7 @@ type TWalletActionModal = {
onClick: VoidFunction;
text: string;
}[];
- description?: string;
+ description?: JSX.Element | string;
hideCloseButton?: React.ComponentProps['hideCloseButton'];
title: string;
};
diff --git a/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx b/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx
index b700cdba2e95..d20b34a13dd1 100644
--- a/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx
+++ b/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx
@@ -11,7 +11,9 @@ import {
LabelPairedSquareListMdRegularIcon,
LegacyClose2pxIcon,
} from '@deriv/quill-icons';
-import { WalletCurrencyIcon, WalletGradientBackground, WalletText } from '../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { WalletCurrencyIcon, WalletGradientBackground } from '../../../../components';
import { WalletListCardBadge } from '../../../../components/WalletListCardBadge';
import useAllBalanceSubscription from '../../../../hooks/useAllBalanceSubscription';
import useDevice from '../../../../hooks/useDevice';
@@ -21,46 +23,54 @@ type TProps = {
hideWalletDetails: boolean;
};
-const realAccountTabs = [
- {
- icon: ,
- path: 'deposit',
- text: 'Deposit',
- },
- {
- icon: ,
- path: 'withdrawal',
- text: 'Withdraw',
- },
- {
- icon: ,
- path: 'account-transfer',
- text: 'Transfer',
- },
- {
- icon: ,
- path: 'transactions',
- text: 'Transactions',
- },
-] as const;
+const getRealAccountTabs = () => {
+ const realAccountTabs = [
+ {
+ icon: ,
+ path: 'deposit',
+ text: ,
+ },
+ {
+ icon: ,
+ path: 'withdrawal',
+ text: ,
+ },
+ {
+ icon: ,
+ path: 'account-transfer',
+ text: ,
+ },
+ {
+ icon: ,
+ path: 'transactions',
+ text: ,
+ },
+ ] as const;
-const virtualAccountTabs = [
- {
- icon: ,
- path: 'account-transfer',
- text: 'Transfer',
- },
- {
- icon: ,
- path: 'transactions',
- text: 'Transactions',
- },
- {
- icon: ,
- path: 'reset-balance',
- text: 'Reset Balance',
- },
-] as const;
+ return realAccountTabs;
+};
+
+const getVirtualAccountTabs = () => {
+ const virtualAccountTabs = [
+ {
+ icon: ,
+ path: 'account-transfer',
+ text: ,
+ },
+ {
+ icon: ,
+ path: 'transactions',
+ text: ,
+ },
+ {
+ icon: ,
+ path: 'reset-balance',
+ text: ,
+ },
+ ] as const;
+
+ return virtualAccountTabs;
+};
const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
const { data: activeWallet } = useActiveWalletAccount();
@@ -71,7 +81,7 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
const location = useLocation();
const accountsActiveTabIndexRef = useRef(location.state?.accountsActiveTabIndex ?? 0);
- const tabs = activeWallet?.is_virtual ? virtualAccountTabs : realAccountTabs;
+ const tabs = activeWallet?.is_virtual ? getVirtualAccountTabs() : getRealAccountTabs();
const isDemo = activeWallet?.is_virtual;
useEffect(() => {
@@ -96,9 +106,9 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
'wallets-cashier-header__details--hide-details': hideWalletDetails,
})}
>
-
+
{activeWallet?.currency} Wallet
-
+
{isDemo && }
{isBalanceLoading ? (
@@ -107,7 +117,7 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
data-testid='dt_wallets_cashier_header_balance_loader'
/>
) : (
-
+
{displayMoney(
balanceData?.[activeWallet?.loginid ?? '']?.balance,
activeWallet?.currency,
@@ -115,7 +125,7 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
fractional_digits: activeWallet?.currency_config?.fractional_digits,
}
)}
-
+
)}
@@ -170,13 +180,13 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => {
>
{tab.icon}
-
{tab.text}
-
+
);
})}
diff --git a/packages/wallets/src/features/cashier/helpers/transaction-helpers.tsx b/packages/wallets/src/features/cashier/helpers/transaction-helpers.tsx
new file mode 100644
index 000000000000..b27a9c05890f
--- /dev/null
+++ b/packages/wallets/src/features/cashier/helpers/transaction-helpers.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+import { useSubscription } from '@deriv/api-v2';
+import { Localize, localize } from '@deriv-com/translations';
+
+type TTransaction = NonNullable<
+ NonNullable>['data']>['cashier_payments']
+>['crypto'][number];
+
+type TStatus = TTransaction['status_code'];
+
+type TDepositStatus = 'CONFIRMED' | 'ERROR' | 'PENDING';
+
+type TWithdrawalStatus = Exclude;
+
+// Since BE sends the `status_code` for both `deposit` and `withdrawal` in the same field,
+// Here we modify the BE type to make `status_code` type more specific to the `transaction_type` field.
+type TModifiedTransaction = Omit &
+ (
+ | { statusCode: TDepositStatus; transactionType: 'deposit' }
+ | { statusCode: TWithdrawalStatus; transactionType: 'withdrawal' }
+ );
+
+export const getStatusName = (statusCode: TModifiedTransaction['statusCode']) => {
+ switch (statusCode) {
+ case 'CONFIRMED':
+ case 'SENT':
+ return ;
+ case 'ERROR':
+ case 'REJECTED':
+ case 'REVERTED':
+ return ;
+ case 'PENDING':
+ case 'PERFORMING_BLOCKCHAIN_TXN':
+ case 'PROCESSING':
+ case 'REVERTING':
+ case 'VERIFIED':
+ return ;
+ case 'CANCELLED':
+ return ;
+ case 'LOCKED':
+ return ;
+ default:
+ return '';
+ }
+};
+
+export const getStatusDescription = (
+ transactionType: TModifiedTransaction['transactionType'],
+ statusCode: TModifiedTransaction['statusCode']
+) => {
+ switch (statusCode) {
+ // deposit-specific:
+ case 'CONFIRMED':
+ return ;
+ case 'PENDING':
+ return (
+
+ );
+ // withdrawal-specific:
+ case 'CANCELLED':
+ return ;
+ case 'LOCKED':
+ return (
+
+ );
+ case 'PERFORMING_BLOCKCHAIN_TXN':
+ return ;
+ case 'PROCESSING':
+ return ;
+ case 'REJECTED':
+ case 'REVERTED':
+ return (
+
+ );
+ case 'REVERTING':
+ case 'VERIFIED':
+ return ;
+ case 'SENT':
+ return ;
+ // both:
+ case 'ERROR':
+ return transactionType === 'deposit' ? (
+
+ ) : (
+
+ );
+ default:
+ return '';
+ }
+};
+
+export const getFormattedConfirmations = (
+ confirmations: TModifiedTransaction['confirmations'],
+ statusCode: TModifiedTransaction['statusCode']
+) => {
+ switch (statusCode) {
+ case 'CONFIRMED':
+ return localize('Confirmed');
+ case 'ERROR':
+ return localize('NA');
+ default:
+ return confirmations?.toString() ?? localize('Pending');
+ }
+};
diff --git a/packages/wallets/src/features/cashier/modules/CashierLocked/__tests__/CashierLocked.spec.tsx b/packages/wallets/src/features/cashier/modules/CashierLocked/__tests__/CashierLocked.spec.tsx
index 50a1bd551c2d..cfe0d861740f 100644
--- a/packages/wallets/src/features/cashier/modules/CashierLocked/__tests__/CashierLocked.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/CashierLocked/__tests__/CashierLocked.spec.tsx
@@ -12,6 +12,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoCurrencyDetails/DepositCryptoCurrencyDetails.tsx b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoCurrencyDetails/DepositCryptoCurrencyDetails.tsx
index 1395ae4a57aa..2ca916545593 100644
--- a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoCurrencyDetails/DepositCryptoCurrencyDetails.tsx
+++ b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoCurrencyDetails/DepositCryptoCurrencyDetails.tsx
@@ -1,14 +1,21 @@
import React from 'react';
import { useActiveWalletAccount } from '@deriv/api-v2';
-import { WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
const DepositCryptoCurrencyDetails = () => {
const { data } = useActiveWalletAccount();
return (
-
- Send only {data?.currency_config?.name} ({data?.currency_config?.display_code}) to this address
-
+
+
+
);
};
diff --git a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.scss b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.scss
index cedbe70b907b..4a5e54e9f449 100644
--- a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.scss
+++ b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.scss
@@ -26,9 +26,6 @@
}
&__note {
- font-size: 1.2rem;
- line-height: 1.8rem;
- text-align: center;
display: flex;
justify-content: center;
diff --git a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.tsx b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.tsx
index 8b8a41ba770b..c79099682093 100644
--- a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.tsx
+++ b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/DepositCryptoDisclaimers.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import { useActiveWalletAccount, useCryptoConfig } from '@deriv/api-v2';
-import { InlineMessage, WalletText } from '../../../../../../components/Base';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { InlineMessage } from '../../../../../../components/Base';
import './DepositCryptoDisclaimers.scss';
// Check with BE to see if we can get the network name from the API.
@@ -24,13 +26,21 @@ const DepositCryptoDisclaimers = () => {
const minimumDepositDisclaimer = data?.currency_config?.is_tUSDT ? (
- A minimum deposit value of {formattedMinimumDepositValue} {currency} is required.
- Otherwise, a fee is applied.
+ ]}
+ i18n_default_text='A minimum deposit value of <0>{{formattedMinimumDepositValue}}0> {{currency}} is required.
+ Otherwise, a fee is applied.'
+ values={{ currency, formattedMinimumDepositValue }}
+ />
) : (
- A minimum deposit value of {formattedMinimumDepositValue} {currency} is required.
- Otherwise, the funds will be lost and cannot be recovered.
+ ]}
+ i18n_default_text='A minimum deposit value of <0>{{formattedMinimumDepositValue}}0> {{currency}} is required.
+ Otherwise, the funds will be lost and cannot be recovered.'
+ values={{ currency, formattedMinimumDepositValue }}
+ />
);
@@ -38,28 +48,34 @@ const DepositCryptoDisclaimers = () => {
-
- To avoid loss of funds:
-
+
+
+
{cryptoConfig?.minimum_deposit && minimumDepositDisclaimer}
- Do not send other cryptocurrencies to this address.
- Make sure to copy your Deriv account address correctly into your crypto wallet.
- In your cryptocurrency wallet, make sure to select{' '}
- {currency && cryptoCurrencyToNetworkMapper[currency]} network when you
- transfer funds to Deriv.
+
+
+
+
+
+
+ ]}
+ i18n_default_text='In your cryptocurrency wallet, make sure to select <0>{{currency}} network0> when you transfer funds to Deriv.'
+ values={{ currency: currency && cryptoCurrencyToNetworkMapper[currency] }}
+ />
-
- Note:
-
-
- You’ll receive an email when your deposit starts being processed.
-
+
+ ]}
+ i18n_default_text="<0>Note:0> You'll receive an email when your deposit starts being processed."
+ />
+
);
diff --git a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/__tests__/DepositCryptoDisclaimers.spec.tsx b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/__tests__/DepositCryptoDisclaimers.spec.tsx
index 5223ac62f884..e6841182d6ed 100644
--- a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/__tests__/DepositCryptoDisclaimers.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoDisclaimers/__tests__/DepositCryptoDisclaimers.spec.tsx
@@ -31,7 +31,7 @@ describe('DepositCryptoDisclaimers', () => {
).toBeInTheDocument();
expect(screen.queryByText(/A minimum deposit value of/)).not.toBeInTheDocument();
expect(
- screen.getByText('You’ll receive an email when your deposit starts being processed.')
+ screen.getByText("You'll receive an email when your deposit starts being processed.")
).toBeInTheDocument();
});
@@ -49,7 +49,7 @@ describe('DepositCryptoDisclaimers', () => {
expect(screen.getByText(/A minimum deposit value of/)).toBeInTheDocument();
expect(screen.getByText(/Ethereum \(ETH\) network/)).toBeInTheDocument();
expect(
- screen.getByText('You’ll receive an email when your deposit starts being processed.')
+ screen.getByText("You'll receive an email when your deposit starts being processed.")
).toBeInTheDocument();
});
@@ -76,7 +76,7 @@ describe('DepositCryptoDisclaimers', () => {
expect(screen.getByText(/Tron \(TRC20\) network/)).toBeInTheDocument();
expect(screen.getByText(/Otherwise, a fee is applied./)).toBeInTheDocument();
expect(
- screen.getByText('You’ll receive an email when your deposit starts being processed.')
+ screen.getByText("You'll receive an email when your deposit starts being processed.")
).toBeInTheDocument();
});
});
diff --git a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoTryFiatOnRamp/DepositCryptoTryFiatOnRamp.tsx b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoTryFiatOnRamp/DepositCryptoTryFiatOnRamp.tsx
index 0383a2969a39..da843c31c16c 100644
--- a/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoTryFiatOnRamp/DepositCryptoTryFiatOnRamp.tsx
+++ b/packages/wallets/src/features/cashier/modules/DepositCrypto/components/DepositCryptoTryFiatOnRamp/DepositCryptoTryFiatOnRamp.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
-import { WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
import './DepositCryptoTryFiatOnRamp.scss';
const DepositCryptoTryFiatOnRamp = () => {
@@ -8,16 +9,18 @@ const DepositCryptoTryFiatOnRamp = () => {
return (
);
};
diff --git a/packages/wallets/src/features/cashier/modules/DepositLocked/__tests__/DepositLocked.spec.tsx b/packages/wallets/src/features/cashier/modules/DepositLocked/__tests__/DepositLocked.spec.tsx
index 8ab9da4418ec..8a5ac6eee522 100644
--- a/packages/wallets/src/features/cashier/modules/DepositLocked/__tests__/DepositLocked.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/DepositLocked/__tests__/DepositLocked.spec.tsx
@@ -21,6 +21,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/FiatOnRamp/FiatOnRamp.tsx b/packages/wallets/src/features/cashier/modules/FiatOnRamp/FiatOnRamp.tsx
index dc01e56989c4..f38b31a37e59 100644
--- a/packages/wallets/src/features/cashier/modules/FiatOnRamp/FiatOnRamp.tsx
+++ b/packages/wallets/src/features/cashier/modules/FiatOnRamp/FiatOnRamp.tsx
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { LegacyArrowLeft2pxIcon } from '@deriv/quill-icons';
-import { Button } from '@deriv-com/ui';
-import { WalletText } from '../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
import { FiatOnRampDisclaimer, FiatOnRampProviderCard } from './components';
import { fiatOnRampProvider } from './constants';
import './FiatOnRamp.scss';
@@ -25,17 +25,19 @@ const FiatOnRamp = () => {
icon={ }
onClick={() => history.push('/wallet/deposit')}
>
- Back
+
-
- Fiat onramp is a cashier service that allows you to convert fiat currencies to
+
+
+
= ({ handleDisclaime
return (
-
- Disclaimer
-
-
- By clicking Continue , you'll be redirected to Banxa, a third-party payment service
+
+
+
+
+ ]}
+ i18n_default_text="By clicking <0>Continue0>, you'll be redirected to Banxa, a third-party payment service
provider. Please note that Deriv is not responsible for the content or services provided by Banxa. If
- you encounter any issues related to Banxa services, you should contact Banxa directly.
-
+ you encounter any issues related to Banxa services, you should contact Banxa directly."
+ />
+
- Back
+
- redirectToBanxa()} size='md'>
- Continue
+ redirectToBanxa()} size='md'>
+
diff --git a/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampProviderCard/FiatOnRampProviderCard.tsx b/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampProviderCard/FiatOnRampProviderCard.tsx
index eae1e0dc5595..a45dcecf9f71 100644
--- a/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampProviderCard/FiatOnRampProviderCard.tsx
+++ b/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampProviderCard/FiatOnRampProviderCard.tsx
@@ -1,16 +1,15 @@
import React, { MouseEventHandler } from 'react';
-import { Button } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
import './FiatOnRampProviderCard.scss';
type TFiatOnRampProvider = {
- description: string;
+ description: React.ReactNode;
getPaymentIcons: () => { icon: JSX.Element; name: string }[];
handleDisclaimer: MouseEventHandler;
icon: React.ReactNode;
- name: string;
+ name: React.ReactNode;
};
-
const FiatOnRampProviderCard: React.FC = ({
description,
getPaymentIcons,
@@ -24,10 +23,10 @@ const FiatOnRampProviderCard: React.FC = ({
{icon}
-
+
{name}
-
-
{description}
+
+
{description}
{paymentIcons.map(paymentIcon => (
= ({
{paymentIcons.map(paymentIcon => (
{paymentIcon.icon}
))}
-
- Select
+
+
);
diff --git a/packages/wallets/src/features/cashier/modules/FiatOnRamp/constants.tsx b/packages/wallets/src/features/cashier/modules/FiatOnRamp/constants.tsx
index 4737da52f69c..96529aad25ca 100644
--- a/packages/wallets/src/features/cashier/modules/FiatOnRamp/constants.tsx
+++ b/packages/wallets/src/features/cashier/modules/FiatOnRamp/constants.tsx
@@ -5,10 +5,12 @@ import {
PaymentMethodMastercardBrandIcon,
PaymentMethodVisaBrandIcon,
} from '@deriv/quill-icons';
+import { Localize } from '@deriv-com/translations';
export const fiatOnRampProvider = {
- description:
- 'A fast and secure fiat-to-crypto payment service. Deposit cryptocurrencies from anywhere in the world using your credit/debit cards and bank transfers.',
+ description: (
+
+ ),
getPaymentIcons: () => [
{
icon:
,
@@ -24,5 +26,5 @@ export const fiatOnRampProvider = {
},
],
icon:
,
- name: 'Banxa',
+ name:
,
};
diff --git a/packages/wallets/src/features/cashier/modules/ResetBalance/ResetBalance.tsx b/packages/wallets/src/features/cashier/modules/ResetBalance/ResetBalance.tsx
index ffbfbc82316c..9d775c853978 100644
--- a/packages/wallets/src/features/cashier/modules/ResetBalance/ResetBalance.tsx
+++ b/packages/wallets/src/features/cashier/modules/ResetBalance/ResetBalance.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { useMutation } from '@deriv/api-v2';
+import { useTranslations } from '@deriv-com/translations';
import { Button } from '@deriv-com/ui';
import { WalletsActionScreen } from '../../../../components';
//TODO: replace with quill-icons
@@ -9,6 +10,7 @@ import IcResetDemoBalanceDone from '../../../../public/images/ic-demo-reset-bala
const ResetBalance = () => {
const history = useHistory();
+ const { localize } = useTranslations();
const { isSuccess: isResetBalanceSuccess, mutate } = useMutation('topup_virtual');
const resetBalance = () => {
@@ -18,8 +20,8 @@ const ResetBalance = () => {
:
}
renderButtons={() => (
@@ -29,10 +31,10 @@ const ResetBalance = () => {
size='lg'
textSize='md'
>
- {isResetBalanceSuccess ? 'Transfer funds' : 'Reset balance'}
+ {isResetBalanceSuccess ? localize('Transfer funds') : localize('Reset balance')}
)}
- title={isResetBalanceSuccess ? 'Success' : 'Reset balance'}
+ title={isResetBalanceSuccess ? localize('Success') : localize('Reset balance')}
/>
);
};
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/TransactionStatus.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/TransactionStatus.tsx
index e3a02e1f7851..ec9087693951 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/TransactionStatus.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/TransactionStatus.tsx
@@ -1,8 +1,8 @@
import React, { useCallback, useEffect } from 'react';
import { useActiveWalletAccount, useCryptoTransactions } from '@deriv/api-v2';
import { LegacyWarningIcon } from '@deriv/quill-icons';
-import { Divider, Loader } from '@deriv-com/ui';
-import { WalletText } from '../../../../components/Base';
+import { Localize } from '@deriv-com/translations';
+import { Divider, Loader, Text } from '@deriv-com/ui';
import { THooks } from '../../../../types';
import { TransactionStatusError } from './components/TransactionStatusError';
import { TransactionStatusSuccess } from './components/TransactionStatusSuccess';
@@ -47,9 +47,9 @@ const TransactionStatus: React.FC
= ({ transactionType }) =>
return (
-
- Transaction status
-
+
+
+
{isError && }
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/CryptoTransaction.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/CryptoTransaction.tsx
index 7ab32dc1a46e..5dbb0867611b 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/CryptoTransaction.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/CryptoTransaction.tsx
@@ -3,12 +3,14 @@ import classNames from 'classnames';
import moment from 'moment';
import { useCancelCryptoTransaction } from '@deriv/api-v2';
import { LegacyClose1pxIcon } from '@deriv/quill-icons';
-import { Button } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { getTruncatedString } from '@deriv/utils';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
import { useModal } from '../../../../../../components/ModalProvider';
import useDevice from '../../../../../../hooks/useDevice';
import { THooks } from '../../../../../../types';
import { WalletActionModal } from '../../../../components/WalletActionModal';
+import { getFormattedConfirmations, getStatusName } from '../../../../helpers/transaction-helpers';
import './CryptoTransaction.scss';
type TCryptoTransaction = {
@@ -26,6 +28,14 @@ const CryptoTransaction: React.FC
= ({
}) => {
const { hide, show } = useModal();
const { isMobile } = useDevice();
+ const { localize } = useTranslations();
+ const formattedTransactionHash = transaction.transaction_hash
+ ? getTruncatedString(transaction.transaction_hash, { type: 'middle' })
+ : localize('Pending');
+ const formattedAddressHash = transaction.address_hash
+ ? getTruncatedString(transaction.address_hash, { type: 'middle' })
+ : localize('NA');
+ const formattedConfirmations = getFormattedConfirmations(transaction.confirmations, transaction.status_code);
const { mutate } = useCancelCryptoTransaction();
@@ -40,30 +50,34 @@ const CryptoTransaction: React.FC = ({
actionButtonsOptions={[
{
onClick: hide,
- text: "No, don't cancel",
+ text: localize("No, don't cancel"),
},
{
isPrimary: true,
onClick: cancelTransaction,
- text: 'Yes, cancel',
+ text: localize('Yes, cancel'),
},
]}
- description='Are you sure you want to cancel this transaction?'
- hideCloseButton={true}
- title='Cancel transaction'
+ description={localize('Are you sure you want to cancel this transaction?')}
+ hideCloseButton
+ title={localize('Cancel transaction')}
/>,
{
defaultRootId: 'wallets_modal_root',
}
);
- }, [cancelTransaction, hide, show]);
+ }, [cancelTransaction, hide, localize, show]);
return (
-
- {transaction.is_deposit ? `Deposit ${currency}` : `Withdrawal ${currency}`}
-
+
+ {transaction.is_deposit ? (
+
+ ) : (
+
+ )}
+
= ({
.replace('_', '-')}`
)}
/>
-
- {transaction.status_name}
-
+
+ {getStatusName(transaction.status_code)}
+
{!!transaction.is_valid_to_cancel && !isMobile && (
= ({
-
+
{transaction.formatted_amount}
-
-
+
+
{moment.unix(transaction.submit_date).utc().format('MMM D, YYYY')}
-
+
{transaction?.transaction_fee && (
-
- Transaction fee: {Number(transaction.transaction_fee).toFixed(currencyDisplayFraction)} {currency}
-
+
+
+
)}
-
- Address:{' '}
-
- {transaction.formatted_address_hash}
-
-
-
- Transaction hash:{' '}
-
- {transaction.formatted_transaction_hash}
-
-
+
+ ,
+ ]}
+ i18n_default_text='Address: <0>{{address}}0>'
+ values={{ address: formattedAddressHash }}
+ />
+
+
+ ,
+ ]}
+ i18n_default_text='Transaction hash: <0>{{hash}}0>'
+ values={{ hash: formattedTransactionHash }}
+ />
+
{transaction.is_deposit && (
-
- Confirmations:{' '}
-
- {transaction.formatted_confirmations}
-
-
+
+ ]}
+ i18n_default_text='Confirmations: <0>{{confirmations}}0>'
+ values={{ confirmations: formattedConfirmations }}
+ />
+
)}
{!!transaction.is_valid_to_cancel && isMobile && (
@@ -142,7 +171,7 @@ const CryptoTransaction: React.FC
= ({
size='sm'
variant='outlined'
>
- Cancel transaction
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/__tests__/CryptoTransaction.spec.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/__tests__/CryptoTransaction.spec.tsx
index 03cdfda9d1ce..0f602279d25e 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/__tests__/CryptoTransaction.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/CryptoTransaction/__tests__/CryptoTransaction.spec.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { useCancelCryptoTransaction } from '@deriv/api-v2';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, within } from '@testing-library/react';
import { ModalProvider } from '../../../../../../../components/ModalProvider';
import CryptoTransaction from '../CryptoTransaction';
@@ -30,10 +30,7 @@ const mockTransaction = {
address_url: '',
amount: 0.0002,
description: '',
- formatted_address_hash: '',
formatted_amount: '',
- formatted_confirmations: 'Pending',
- formatted_transaction_hash: 'Pending',
id: '',
is_deposit: false,
is_valid_to_cancel: 1 as const,
@@ -70,10 +67,7 @@ describe('CryptoTransaction', () => {
address_url: '',
amount: 0.0002,
description: '',
- formatted_address_hash: '',
formatted_amount: '',
- formatted_confirmations: 'Pending',
- formatted_transaction_hash: 'Pending',
id: '',
is_deposit: true,
is_valid_to_cancel: 1 as const,
@@ -82,6 +76,7 @@ describe('CryptoTransaction', () => {
status_message: '',
status_name: '',
submit_date: 123456,
+ transaction_hash: '',
transaction_type: 'withdrawal' as const,
};
(useCancelCryptoTransaction as jest.Mock).mockReturnValue({ mutate: jest.fn() });
@@ -92,9 +87,12 @@ describe('CryptoTransaction', () => {
);
+ const confirmationElement = screen.getByText(/Confirmations/);
+ const pendingElement = within(confirmationElement).getByText(/Pending/);
+
expect(screen.getByText('Deposit BTC')).toBeInTheDocument();
- expect(screen.getByText(/Confirmations/)).toBeInTheDocument();
- expect(screen.getAllByText(/Pending/)[1]).toBeInTheDocument();
+ expect(confirmationElement).toBeInTheDocument();
+ expect(pendingElement).toBeInTheDocument();
});
it('should open modal when cancel button is clicked', async () => {
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusError/TransactionStatusError.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusError/TransactionStatusError.tsx
index 044f32fe8be2..08e2aa845bb8 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusError/TransactionStatusError.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusError/TransactionStatusError.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { Button, Divider } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Localize } from '@deriv-com/translations';
+import { Button, Divider, Text } from '@deriv-com/ui';
type TTransactionStatusError = {
refresh: VoidFunction;
@@ -8,12 +8,12 @@ type TTransactionStatusError = {
const TransactionStatusError: React.FC
= ({ refresh }) => (
-
- Unfortunately, we cannot retrieve the information at this time.
-
+
+
+
- Refresh
+
);
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/TransactionStatusSuccess.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/TransactionStatusSuccess.tsx
index 5c0f12544868..7577b22eafc2 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/TransactionStatusSuccess.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/TransactionStatusSuccess.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
-import { Button, Divider } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Localize } from '@deriv-com/translations';
+import { Button, Divider, Text } from '@deriv-com/ui';
import { THooks } from '../../../../../../types';
import { CryptoTransaction } from '../CryptoTransaction';
@@ -55,13 +55,15 @@ const TransactionStatusSuccess: React.FC = ({ transac
size='sm'
variant='outlined'
>
- View more
+
)}
) : (
- No recent transactions.
+
+
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/__tests__/TransactionStatusSuccess.spec.tsx b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/__tests__/TransactionStatusSuccess.spec.tsx
index 77cb510f4b8e..49b35d08b96a 100644
--- a/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/__tests__/TransactionStatusSuccess.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/TransactionStatus/components/TransactionStatusSuccess/__tests__/TransactionStatusSuccess.spec.tsx
@@ -17,10 +17,7 @@ const mockTransactions = [
address_url: '',
amount: 0.0001,
description: '',
- formatted_address_hash: '',
formatted_amount: '0.00010000 BTC',
- formatted_confirmations: '',
- formatted_transaction_hash: '',
id: '',
is_deposit: false,
is_valid_to_cancel: 1 as const,
@@ -29,6 +26,7 @@ const mockTransactions = [
status_message: '',
status_name: '',
submit_date: 123456,
+ transaction_hash: '',
transaction_type: 'withdrawal' as const,
},
];
@@ -104,7 +102,7 @@ describe('TransactionStatusSuccess', () => {
jest.clearAllMocks();
});
- it('should render winthdrawal info for withdrawal transactions', () => {
+ it('should render withdrawal info for withdrawal transactions', () => {
render(
@@ -119,7 +117,7 @@ describe('TransactionStatusSuccess', () => {
);
- expect(screen.getByText('Withdrawal')).toBeInTheDocument();
+ expect(screen.getByText(/Withdrawal/)).toBeInTheDocument();
expect(screen.getByText('0.00010000 BTC')).toBeInTheDocument();
expect(screen.queryByText('No recent transactions.')).not.toBeInTheDocument();
expect(screen.queryByText('View more')).not.toBeInTheDocument();
@@ -132,10 +130,7 @@ describe('TransactionStatusSuccess', () => {
address_url: '',
amount: 0.0001,
description: '',
- formatted_address_hash: '',
formatted_amount: '0.00010000 BTC',
- formatted_confirmations: '',
- formatted_transaction_hash: '',
id: '',
is_deposit: true,
is_valid_to_cancel: 1 as const,
@@ -144,6 +139,7 @@ describe('TransactionStatusSuccess', () => {
status_message: '',
status_name: '',
submit_date: 123456,
+ transaction_hash: '',
transaction_type: 'withdrawal' as const,
},
];
@@ -162,7 +158,7 @@ describe('TransactionStatusSuccess', () => {
);
- expect(screen.getByText('Deposit')).toBeInTheDocument();
+ expect(screen.getByText(/Deposit/)).toBeInTheDocument();
expect(screen.getByText('0.00010000 BTC')).toBeInTheDocument();
expect(screen.queryByText('No recent transactions.')).not.toBeInTheDocument();
expect(screen.queryByText('View more')).not.toBeInTheDocument();
@@ -180,7 +176,7 @@ describe('TransactionStatusSuccess', () => {
);
expect(screen.getByText('No recent transactions.')).toBeInTheDocument();
- expect(screen.queryByText('Deposit')).not.toBeInTheDocument();
+ expect(screen.queryByText(/Deposit/)).not.toBeInTheDocument();
expect(screen.queryByText('Withdrawal')).not.toBeInTheDocument();
expect(screen.queryByText('View more')).not.toBeInTheDocument();
});
@@ -195,10 +191,7 @@ describe('TransactionStatusSuccess', () => {
address_url: '',
amount: 0.0001,
description: '',
- formatted_address_hash: '',
formatted_amount: '',
- formatted_confirmations: '',
- formatted_transaction_hash: '',
id: `transaction_${i}`,
is_deposit: false,
is_valid_to_cancel: 1 as const,
@@ -207,6 +200,7 @@ describe('TransactionStatusSuccess', () => {
status_message: '',
status_name: '',
submit_date: 123456,
+ transaction_hash: '',
transaction_type: 'withdrawal' as const,
};
@@ -228,7 +222,7 @@ describe('TransactionStatusSuccess', () => {
);
expect(screen.queryByText('No recent transactions.')).not.toBeInTheDocument();
- expect(screen.getAllByText('Withdrawal')[0]).toBeInTheDocument();
+ expect(screen.getAllByText(/Withdrawal/)[0]).toBeInTheDocument();
expect(screen.getAllByText('0.00010000 BTC')[0]).toBeInTheDocument();
expect(screen.getByText('View more')).toBeInTheDocument();
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/Transactions.tsx b/packages/wallets/src/features/cashier/modules/Transactions/Transactions.tsx
index 0dd115074d65..8edb438cb0e3 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/Transactions.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/Transactions.tsx
@@ -3,10 +3,12 @@ import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { useActiveWalletAccount, useCurrencyConfig } from '@deriv/api-v2';
import { LegacyFilter1pxIcon } from '@deriv/quill-icons';
-import { Dropdown } from '@deriv-com/ui';
-import { ToggleSwitch, WalletText } from '../../../../components';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Dropdown, Text } from '@deriv-com/ui';
+import { ToggleSwitch } from '../../../../components';
import useDevice from '../../../../hooks/useDevice';
import { TransactionsCompleted, TransactionsCompletedDemoResetBalance, TransactionsPending } from './components';
+import { getTransactionLabels } from './constants';
import './Transactions.scss';
type TTransactionsPendingFilter = React.ComponentProps['filter'];
@@ -31,6 +33,8 @@ const filtersMapper: Record> = {
const Transactions = () => {
const { data: wallet } = useActiveWalletAccount();
+ const { localize } = useTranslations();
+
const { isLoading } = useCurrencyConfig();
const { isMobile } = useDevice();
@@ -52,8 +56,9 @@ const Transactions = () => {
.map(key => ({
text:
key === 'deposit' && wallet?.is_virtual
- ? 'Reset balance'
- : key.replace(/^\w/, c => c.toUpperCase()),
+ ? getTransactionLabels().reset_balance
+ : //@ts-expect-error we only need partial filter values
+ getTransactionLabels()[key],
value: key,
})),
[isPendingActive, wallet?.is_virtual]
@@ -83,7 +88,9 @@ const Transactions = () => {
{wallet?.is_crypto && (
- Pending Transactions
+
+
+
setIsPendingActive(!isPendingActive)} value={isPendingActive} />
)}
@@ -92,7 +99,7 @@ const Transactions = () => {
data-testid='dt_wallets_transactions_dropdown'
icon={
}
isFullWidth
- label='Filter'
+ label={localize('Filter')}
list={filterOptionsList}
name='wallets-transactions__dropdown'
onSelect={value => {
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/TransactionsCompleted.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/TransactionsCompleted.tsx
index 29300313bc80..377936312506 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/TransactionsCompleted.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/TransactionsCompleted.tsx
@@ -2,8 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import moment from 'moment';
import { useActiveWalletAccount, useAllAccountsList, useInfiniteTransactions } from '@deriv/api-v2';
import { TSocketRequestPayload } from '@deriv/api-v2/types';
-import { Loader } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Loader, Text } from '@deriv-com/ui';
import { useCashierScroll } from '../../../../context';
import { TransactionsCompletedRow } from '../TransactionsCompletedRow';
import { TransactionsNoDataState } from '../TransactionsNoDataState';
@@ -70,10 +69,10 @@ const TransactionsCompleted: React.FC
= ({ filter }) => {
groupBy={['date']}
rowGroupRender={transaction => (
-
+
{transaction.transaction_time &&
moment.unix(transaction.transaction_time).format('DD MMM YYYY')}
-
+
)}
rowRender={transaction => (
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/__tests__/TransactionsCompleted.spec.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/__tests__/TransactionsCompleted.spec.tsx
index ea8d18cab3fa..8fe953928676 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/__tests__/TransactionsCompleted.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompleted/__tests__/TransactionsCompleted.spec.tsx
@@ -14,6 +14,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/TransactionsCompletedDemoResetBalance.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/TransactionsCompletedDemoResetBalance.tsx
index ff403adcb55d..897b1898c77f 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/TransactionsCompletedDemoResetBalance.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/TransactionsCompletedDemoResetBalance.tsx
@@ -1,8 +1,7 @@
import React, { useEffect } from 'react';
import moment from 'moment';
import { useActiveWalletAccount, useAllAccountsList, useTransactions } from '@deriv/api-v2';
-import { Loader } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Loader, Text } from '@deriv-com/ui';
import { TransactionsCompletedRow } from '../TransactionsCompletedRow';
import { TransactionsNoDataState } from '../TransactionsNoDataState';
import { TransactionsTable } from '../TransactionsTable';
@@ -51,10 +50,10 @@ const TransactionsCompletedDemoResetBalance: React.FC = () => {
groupBy={['date']}
rowGroupRender={transaction => (
-
+
{transaction.transaction_time &&
moment.unix(transaction.transaction_time).format('DD MMM YYYY')}
-
+
)}
rowRender={transaction => (
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/__tests__/TransactionsCompletedDemoResetBalance.spec.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/__tests__/TransactionsCompletedDemoResetBalance.spec.tsx
index 8d837881c01c..783b67b21a30 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/__tests__/TransactionsCompletedDemoResetBalance.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedDemoResetBalance/__tests__/TransactionsCompletedDemoResetBalance.spec.tsx
@@ -15,6 +15,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/TransactionsCompletedRow.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/TransactionsCompletedRow.tsx
index 5c0023bec26c..6dd80fe62c2d 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/TransactionsCompletedRow.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/TransactionsCompletedRow.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { Divider } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Divider, Text } from '@deriv-com/ui';
import { THooks } from '../../../../../../types';
-import { TransactionsCompletedRowAccountDetails } from './components/TransactionsCompletedRowAccountDetails';
-import { TransactionsCompletedRowTransferAccountDetails } from './components/TransactionsCompletedRowTransferAccountDetails';
+import { getTransactionLabels } from '../../constants';
+import { TransactionsCompletedRowAccountDetails, TransactionsCompletedRowTransferAccountDetails } from './components';
import './TransactionsCompletedRow.scss';
type TProps = {
@@ -13,14 +13,19 @@ type TProps = {
};
const TransactionsCompletedRow: React.FC = ({ accounts, transaction, wallet }) => {
+ const { localize } = useTranslations();
+
if (!transaction.action_type || !transaction.amount) return null;
const displayCurrency = wallet?.currency_config?.display_code || 'USD';
const displayWalletName = `${displayCurrency} Wallet`;
- const displayActionType =
+ const displayNonTransferActionType =
wallet.is_virtual && ['deposit', 'withdrawal'].includes(transaction.action_type)
- ? 'Reset balance'
- : transaction.action_type.replace(/^\w/, c => c.toUpperCase());
+ ? getTransactionLabels().reset_balance
+ : //@ts-expect-error we only need partial action types
+ getTransactionLabels()[transaction.action_type];
+ const displayTransferActionType =
+ transaction.from?.loginid === wallet?.loginid ? localize('Transfer to') : localize('Transfer from');
return (
@@ -32,13 +37,13 @@ const TransactionsCompletedRow: React.FC = ({ accounts, transaction, wal
actionType={transaction.action_type}
currency={wallet?.currency ?? 'USD'}
displayAccountName={displayWalletName}
- displayActionType={displayActionType}
+ displayActionType={displayNonTransferActionType}
isDemo={Boolean(wallet?.is_virtual)}
/>
) : (
loginid !== wallet?.loginid
@@ -47,13 +52,18 @@ const TransactionsCompletedRow: React.FC = ({ accounts, transaction, wal
/>
)}
- 0 ? 'success' : 'error'} size='xs' weight='bold'>
+ 0 ? 'success' : 'error'} size='xs' weight='bold'>
{transaction.amount && transaction.amount > 0 ? '+' : ''}
{transaction.display_amount}
-
-
- Balance: {transaction.display_balance_after}
-
+
+
+
+
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowAccountDetails/TransactionsCompletedRowAccountDetails.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowAccountDetails/TransactionsCompletedRowAccountDetails.tsx
index a40aff3ce171..e73ac4d278e6 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowAccountDetails/TransactionsCompletedRowAccountDetails.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowAccountDetails/TransactionsCompletedRowAccountDetails.tsx
@@ -9,7 +9,7 @@ type TProps = {
accountType: string;
actionType: NonNullable<(THooks.InfiniteTransactions | THooks.Transactions)['action_type']>;
currency: string;
- displayAccountName: string;
+ displayAccountName: JSX.Element | string;
displayActionType: string;
isDemo: boolean;
isInterWallet?: boolean;
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/TransactionsCompletedRowTransferAccountDetails.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/TransactionsCompletedRowTransferAccountDetails.tsx
index 5d98cea24278..f51c0edc6cb5 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/TransactionsCompletedRowTransferAccountDetails.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/TransactionsCompletedRowTransferAccountDetails.tsx
@@ -6,11 +6,11 @@ import { TransactionsCompletedRowAccountDetails } from '../TransactionsCompleted
type TProps = {
accounts: THooks.AllAccountsList;
- direction: 'from' | 'to';
+ displayActionType: string;
loginid: string;
};
-const TransactionsCompletedRowTransferAccountDetails: React.FC = ({ accounts, direction, loginid }) => {
+const TransactionsCompletedRowTransferAccountDetails: React.FC = ({ accounts, displayActionType, loginid }) => {
const { data: activeWallet } = useActiveWalletAccount();
const wallet = accounts.wallets?.find(account => account.loginid === loginid);
@@ -39,7 +39,7 @@ const TransactionsCompletedRowTransferAccountDetails: React.FC = ({ acco
actionType='transfer'
currency={transferAccount.currency ?? 'USD'}
displayAccountName={displayAccountName ?? ''}
- displayActionType={`Transfer ${direction}`}
+ displayActionType={displayActionType}
isDemo={Boolean(transferAccount.is_virtual)}
isInterWallet={transferAccount === wallet}
mt5Group={transferAccount === mt5Account ? mt5Account.group : undefined}
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/__tests__/TransactionsCompletedRowTransferAccountDetails.spec.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/__tests__/TransactionsCompletedRowTransferAccountDetails.spec.tsx
index 97aa837e244f..9ea6f32ec9e9 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/__tests__/TransactionsCompletedRowTransferAccountDetails.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/TransactionsCompletedRowTransferAccountDetails/__tests__/TransactionsCompletedRowTransferAccountDetails.spec.tsx
@@ -89,7 +89,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
render(
);
@@ -107,7 +107,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
render(
);
@@ -125,7 +125,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
render(
);
@@ -143,7 +143,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
render(
);
@@ -162,7 +162,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
render(
);
@@ -180,7 +180,7 @@ describe('TransactionsCompletedRowTransferAccountDetails', () => {
const { container } = render(
);
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/index.ts b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/index.ts
new file mode 100644
index 000000000000..2a42d04fab3a
--- /dev/null
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsCompletedRow/components/index.ts
@@ -0,0 +1,4 @@
+import { TransactionsCompletedRowAccountDetails } from './TransactionsCompletedRowAccountDetails';
+import { TransactionsCompletedRowTransferAccountDetails } from './TransactionsCompletedRowTransferAccountDetails';
+
+export { TransactionsCompletedRowAccountDetails, TransactionsCompletedRowTransferAccountDetails };
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsNoDataState/TransactionsNoDataState.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsNoDataState/TransactionsNoDataState.tsx
index b5b1a1f3de12..12c0d47ee839 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsNoDataState/TransactionsNoDataState.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsNoDataState/TransactionsNoDataState.tsx
@@ -1,13 +1,16 @@
import React from 'react';
+import { useTranslations } from '@deriv-com/translations';
+import { WalletsActionScreen } from '../../../../../../components';
//TODO: replace with quill-icons
import NoRecentTransactions from '../../../../../../public/images/no-recent-transactions.svg';
-import { WalletsActionScreen } from '../../../../../../components';
import './TransactionsNoDataState.scss';
const TransactionsNoDataState = () => {
+ const { localize } = useTranslations();
+
return (
- } title={'No transactions found'} />
+ } title={localize('No transactions found')} />
);
};
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/TransactionsPending.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/TransactionsPending.tsx
index ba8a4670d640..21c95c0dbfc6 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/TransactionsPending.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/TransactionsPending.tsx
@@ -1,8 +1,7 @@
import React, { useEffect } from 'react';
import moment from 'moment';
import { useCryptoTransactions } from '@deriv/api-v2';
-import { Loader } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { Loader, Text } from '@deriv-com/ui';
import { TransactionsNoDataState } from '../TransactionsNoDataState';
import { TransactionsPendingRow } from '../TransactionsPendingRow';
import { TransactionsTable } from '../TransactionsTable';
@@ -42,9 +41,9 @@ const TransactionsPending: React.FC = ({ filter = 'all' }) => {
groupBy={['date']}
rowGroupRender={transaction => (
-
+
{transaction.submit_date && moment.unix(transaction.submit_date).format('DD MMM YYYY')}
-
+
)}
rowRender={transaction => }
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/__tests__/TransactionsPending.spec.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/__tests__/TransactionsPending.spec.tsx
index 49a9911c9152..92d2738e15d1 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/__tests__/TransactionsPending.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPending/__tests__/TransactionsPending.spec.tsx
@@ -19,6 +19,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/TransactionsPendingRow.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/TransactionsPendingRow.tsx
index a0db1d26f8f9..0dd484fd9e13 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/TransactionsPendingRow.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/TransactionsPendingRow.tsx
@@ -3,13 +3,20 @@ import classNames from 'classnames';
import moment from 'moment';
import { useActiveWalletAccount, useCancelCryptoTransaction } from '@deriv/api-v2';
import { LegacyClose1pxIcon } from '@deriv/quill-icons';
-import { Button, Divider, Tooltip } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components/Base';
+import { getTruncatedString } from '@deriv/utils';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Button, Divider, Text, Tooltip } from '@deriv-com/ui';
import { useModal } from '../../../../../../components/ModalProvider';
import { WalletCurrencyCard } from '../../../../../../components/WalletCurrencyCard';
import useDevice from '../../../../../../hooks/useDevice';
import { THooks } from '../../../../../../types';
import { WalletActionModal } from '../../../../components/WalletActionModal';
+import {
+ getFormattedConfirmations,
+ getStatusDescription,
+ getStatusName,
+} from '../../../../helpers/transaction-helpers';
+import { getTransactionLabels } from '../../constants';
import { TransactionsPendingRowField } from './components/TransactionsPendingRowField';
import './TransactionsPendingRow.scss';
@@ -20,8 +27,17 @@ type TProps = {
const TransactionsPendingRow: React.FC = ({ transaction }) => {
const { data } = useActiveWalletAccount();
const { isMobile } = useDevice();
+ const { localize } = useTranslations();
const displayCode = useMemo(() => data?.currency_config?.display_code || 'USD', [data]);
const modal = useModal();
+ const formattedTransactionHash = transaction.transaction_hash
+ ? getTruncatedString(transaction.transaction_hash, { type: 'middle' })
+ : localize('Pending');
+ const formattedAddressHash = transaction.address_hash
+ ? getTruncatedString(transaction.address_hash, { type: 'middle' })
+ : localize('NA');
+ const formattedConfirmations = getFormattedConfirmations(transaction.confirmations, transaction.status_code);
+ const statusDescription = getStatusDescription(transaction.transaction_type, transaction.status_code);
const { mutate } = useCancelCryptoTransaction();
@@ -36,21 +52,21 @@ const TransactionsPendingRow: React.FC = ({ transaction }) => {
actionButtonsOptions={[
{
onClick: modal.hide,
- text: "No, don't cancel",
+ text: localize("No, don't cancel"),
},
{
isPrimary: true,
onClick: cancelTransaction,
- text: 'Yes, cancel',
+ text: localize('Yes, cancel'),
},
]}
- description='Are you sure you want to cancel this transaction?'
+ description={localize('Are you sure you want to cancel this transaction?')}
hideCloseButton
- title='Cancel transaction'
+ title={localize('Cancel transaction')}
/>,
{ defaultRootId: 'wallets_modal_root' }
);
- }, [cancelTransaction, modal]);
+ }, [cancelTransaction, localize, modal]);
const onMobileStatusClick = useCallback(() => {
if (isMobile) {
@@ -60,17 +76,17 @@ const TransactionsPendingRow: React.FC = ({ transaction }) => {
{
isPrimary: true,
onClick: modal.hide,
- text: 'Ok',
+ text: localize('Ok'),
},
]}
- description={transaction.description}
+ description={statusDescription}
hideCloseButton
- title='Transaction details'
+ title={localize('Transaction details')}
/>,
{ defaultRootId: 'wallets_modal_root' }
);
}
- }, [isMobile, modal, transaction.description]);
+ }, [isMobile, localize, modal, statusDescription]);
return (
@@ -79,13 +95,12 @@ const TransactionsPendingRow: React.FC = ({ transaction }) => {
-
- {transaction.transaction_type.charAt(0).toUpperCase() +
- transaction.transaction_type.slice(1)}
-
-
+
+ {getTransactionLabels()[transaction.transaction_type]}
+
+
{displayCode} Wallet
-
+
@@ -95,40 +110,40 @@ const TransactionsPendingRow: React.FC
= ({ transaction }) => {
transaction.transaction_url
? {
link: transaction.transaction_url,
- text: 'View transaction hash on Blockchain',
+ text: localize('View transaction hash on Blockchain'),
tooltipAlignment: 'right',
}
: undefined
}
- name='Transaction hash'
- value={transaction.formatted_transaction_hash}
+ name={localize('Transaction hash')}
+ value={formattedTransactionHash}
/>
{isMobile && (
= ({ transaction }) => {
)}
= ({ transaction }) => {
/>
{!isMobile && (
- = ({ transaction }) => {
>
{transaction.is_deposit ? '+' : '-'}
{transaction.formatted_amount}
-
+
)}
@@ -170,7 +185,7 @@ const TransactionsPendingRow: React.FC = ({ transaction }) => {
data-testid='dt_transaction_status_button'
hideTooltip={isMobile}
onClick={onMobileStatusClick}
- tooltipContent={transaction.description}
+ tooltipContent={statusDescription}
tooltipPosition='left'
>
= ({ transaction }) => {
.replace('_', '-')}`
)}
/>
-
- {transaction.status_name}
-
+
+ {getStatusName(transaction.status_code)}
+
{!isMobile && !!transaction.is_valid_to_cancel && (
= ({ transaction }) => {
size='sm'
variant='outlined'
>
- Cancel transaction
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/__tests__/TransactionsPendingRow.spec.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/__tests__/TransactionsPendingRow.spec.tsx
index 4256a58ad2ab..f3fc3372ead1 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/__tests__/TransactionsPendingRow.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/__tests__/TransactionsPendingRow.spec.tsx
@@ -37,10 +37,7 @@ const mockWithdrawal = {
address_url: '',
amount: 0.0002,
description: '',
- formatted_address_hash: '',
formatted_amount: '',
- formatted_confirmations: 'Pending',
- formatted_transaction_hash: 'Pending',
id: '0123',
is_deposit: false,
is_valid_to_cancel: 1 as const,
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/components/TransactionsPendingRowField/TransactionsPendingRowField.tsx b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/components/TransactionsPendingRowField/TransactionsPendingRowField.tsx
index dd65528edd10..5f282beb4485 100644
--- a/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/components/TransactionsPendingRowField/TransactionsPendingRowField.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transactions/components/TransactionsPendingRow/components/TransactionsPendingRowField/TransactionsPendingRowField.tsx
@@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
-import { Tooltip } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../../../components/Base';
+import { useTranslations } from '@deriv-com/translations';
+import { Text, Tooltip } from '@deriv-com/ui';
import { useModal } from '../../../../../../../../components/ModalProvider';
import useDevice from '../../../../../../../../hooks/useDevice';
import { WalletActionModal } from '../../../../../../components/WalletActionModal';
@@ -15,12 +15,13 @@ type TProps = {
tooltipAlignment?: React.ComponentProps['tooltipPosition'];
};
name: string;
- value: string;
- valueTextProps?: Omit, 'children'>;
+ value: JSX.Element | string;
+ valueTextProps?: Omit, 'children'>;
};
const TransactionsPendingRowField: React.FC = ({ className, hint, name, value, valueTextProps }) => {
const { isMobile } = useDevice();
+ const { localize } = useTranslations();
const { show } = useModal();
const onValueClick = useCallback(() => {
@@ -32,23 +33,23 @@ const TransactionsPendingRowField: React.FC = ({ className, hint, name,
{
isPrimary: true,
onClick: () => window.open(hint?.link),
- text: 'View',
+ text: localize('View'),
},
]
: []
}
description={hint?.text}
- title='Transaction details'
+ title={localize('Transaction details')}
/>,
{ defaultRootId: 'wallets_modal_root' }
);
- }, [hint, show]);
+ }, [hint?.link, hint?.text, localize, show]);
return (
-
+
{name}
-
+
{hint ? (
= ({ className, hint, name,
tooltipContent={hint.text}
tooltipPosition={hint.tooltipAlignment}
>
-
+
{isMobile ? (
{value}
@@ -71,12 +72,12 @@ const TransactionsPendingRowField: React.FC = ({ className, hint, name,
{value}
)}
-
+
) : (
-
+
{value}
-
+
)}
);
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/constants/constants.ts b/packages/wallets/src/features/cashier/modules/Transactions/constants/constants.ts
new file mode 100644
index 000000000000..09a31f8baeba
--- /dev/null
+++ b/packages/wallets/src/features/cashier/modules/Transactions/constants/constants.ts
@@ -0,0 +1,9 @@
+import { localize } from '@deriv-com/translations';
+
+export const getTransactionLabels = () => ({
+ all: localize('All'),
+ deposit: localize('Deposit'),
+ reset_balance: localize('Reset balance'),
+ transfer: localize('Transfer'),
+ withdrawal: localize('Withdrawal'),
+});
diff --git a/packages/wallets/src/features/cashier/modules/Transactions/constants/index.ts b/packages/wallets/src/features/cashier/modules/Transactions/constants/index.ts
new file mode 100644
index 000000000000..c94f80f843a1
--- /dev/null
+++ b/packages/wallets/src/features/cashier/modules/Transactions/constants/index.ts
@@ -0,0 +1 @@
+export * from './constants';
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferForm/TransferForm.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferForm/TransferForm.tsx
index f04a81ffc37d..6a148a8b3db4 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferForm/TransferForm.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferForm/TransferForm.tsx
@@ -1,5 +1,6 @@
import React, { useCallback, useRef } from 'react';
import { Formik } from 'formik';
+import { Localize } from '@deriv-com/translations';
import { Button, Loader } from '@deriv-com/ui';
import useDevice from '../../../../../../hooks/useDevice';
import { useTransfer } from '../../provider';
@@ -60,7 +61,7 @@ const TransferForm = () => {
textSize={isMobile ? 'sm' : 'md'}
type='submit'
>
- Transfer
+
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountCard/TransferFormAccountCard.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountCard/TransferFormAccountCard.tsx
index d711b320b278..ed2c6f985b69 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountCard/TransferFormAccountCard.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountCard/TransferFormAccountCard.tsx
@@ -1,11 +1,8 @@
import React from 'react';
import classNames from 'classnames';
-import {
- WalletCurrencyCard,
- WalletListCardBadge,
- WalletMarketCurrencyIcon,
- WalletText,
-} from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { WalletCurrencyCard, WalletListCardBadge, WalletMarketCurrencyIcon } from '../../../../../../components';
import useDevice from '../../../../../../hooks/useDevice';
import { TPlatforms } from '../../../../../../types';
import type { TAccount } from '../../types';
@@ -50,10 +47,17 @@ const TransferFormAccountCard: React.FC = ({ account, type = 'modal' })
-
+
{account?.accountName}
-
- Balance: {account?.displayBalance}
+
+
+
+
{isModal && !!account?.demo_account && (
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/TransferFormAccountSelection.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/TransferFormAccountSelection.tsx
index 973d58fd6567..6d5629ac3ee9 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/TransferFormAccountSelection.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/TransferFormAccountSelection.tsx
@@ -1,8 +1,8 @@
import React, { useMemo } from 'react';
import classNames from 'classnames';
import { LegacyClose2pxIcon } from '@deriv/quill-icons';
-import { Divider } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Divider, Text } from '@deriv-com/ui';
import { useModal } from '../../../../../../components/ModalProvider';
import useDevice from '../../../../../../hooks/useDevice';
import type { TAccount, TAccountsList } from '../../types';
@@ -13,7 +13,8 @@ type TProps = {
accountsList: TAccountsList;
activeWallet: TAccount;
fromAccount?: TAccount;
- label: 'Transfer from' | 'Transfer to';
+ isFromAccountDropdown: boolean;
+ label: string;
onSelect: (value?: TAccount) => void;
selectedAccount?: TAccount;
toAccount?: TAccount;
@@ -30,6 +31,7 @@ const TransferFormAccountSelection: React.FC = ({
accountsList,
activeWallet,
fromAccount,
+ isFromAccountDropdown,
label,
onSelect,
selectedAccount,
@@ -39,12 +41,26 @@ const TransferFormAccountSelection: React.FC = ({
const modal = useModal();
const transferToHint = useMemo(() => {
- const isTransferToHintVisible = label === 'Transfer to' && toAccount?.loginid === activeWallet?.loginid;
+ const isTransferToHintVisible = !isFromAccountDropdown && toAccount?.loginid === activeWallet?.loginid;
- return isTransferToHintVisible
- ? `You can only transfers funds from the ${fromAccount?.accountName} to the linked ${activeWallet?.accountName}.`
- : '';
- }, [activeWallet?.accountName, activeWallet?.loginid, fromAccount?.accountName, label, toAccount?.loginid]);
+ return isTransferToHintVisible ? (
+
+ ) : (
+ ''
+ );
+ }, [
+ activeWallet?.accountName,
+ activeWallet?.loginid,
+ fromAccount?.accountName,
+ isFromAccountDropdown,
+ toAccount?.loginid,
+ ]);
const isSingleAccountsGroup = useMemo(
() => Object.values(accountsList).filter(accounts => accounts.length > 0).length === 1,
@@ -55,9 +71,9 @@ const TransferFormAccountSelection: React.FC = ({
-
+
{label}
-
+
= ({
if (accounts.length === 0) return null;
const groupTitle =
- accountsGroupName === 'tradingAccounts'
- ? `Trading accounts linked with ${activeWallet?.currencyConfig?.display_code} Wallet`
- : 'Wallets';
+ accountsGroupName === 'tradingAccounts' ? (
+
+ ) : (
+
+ );
const isLastAccountsGroup = index === Object.keys(accountsList).length - 1;
const shouldShowDivider = !isMobile && !isSingleAccountsGroup && !isLastAccountsGroup;
@@ -85,9 +108,9 @@ const TransferFormAccountSelection: React.FC = ({
data-testid='dt_wallets_transfer_form_account_selection_accounts_group'
>
-
+
{groupTitle}
-
+
{isMobile && }
@@ -115,9 +138,9 @@ const TransferFormAccountSelection: React.FC
= ({
})}
{transferToHint && (
-
+
{transferToHint}
-
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/__tests__/TransferFormAccountSelection.spec.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/__tests__/TransferFormAccountSelection.spec.tsx
index 0970124bd11d..ae8795bb7585 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/__tests__/TransferFormAccountSelection.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAccountSelection/__tests__/TransferFormAccountSelection.spec.tsx
@@ -5,6 +5,7 @@ import useDevice from '../../../../../../../hooks/useDevice';
import TransferFormAccountSelection from '../TransferFormAccountSelection';
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Divider: jest.fn(() =>
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAmountInput/TransferFormAmountInput.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAmountInput/TransferFormAmountInput.tsx
index e3fe301f6eab..d30d1f55fc74 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAmountInput/TransferFormAmountInput.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormAmountInput/TransferFormAmountInput.tsx
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useFormikContext } from 'formik';
import { useDebounce } from 'usehooks-ts';
+import { Localize, useTranslations } from '@deriv-com/translations';
import { Button } from '@deriv-com/ui';
import { ATMAmountInput, Timer } from '../../../../../../components';
import useInputDecimalFormatter from '../../../../../../hooks/useInputDecimalFormatter';
@@ -19,6 +20,7 @@ const DEBOUNCE_DELAY_MS = 500;
const TransferFormAmountInput: React.FC = ({ fieldName }) => {
const { setFieldValue, setValues, values } = useFormikContext();
const { fromAccount, fromAmount, toAccount, toAmount } = values;
+ const { localize } = useTranslations();
const {
USDExchangeRates,
@@ -47,8 +49,8 @@ const TransferFormAmountInput: React.FC = ({ fieldName }) => {
const amountValue = isFromAmountField ? fromAmount : toAmount;
const debouncedAmountValue = useDebounce(amountValue, DEBOUNCE_DELAY_MS);
- const toAmountLabel = isSameCurrency || !toAccount ? 'Amount you receive' : 'Estimated amount';
- const amountLabel = isFromAmountField ? 'Amount you send' : toAmountLabel;
+ const toAmountLabel = isSameCurrency || !toAccount ? localize('Amount you receive') : localize('Estimated amount');
+ const amountLabel = isFromAmountField ? localize('Amount you send') : toAmountLabel;
const currency = isFromAmountField ? fromAccount?.currency : toAccount?.currency;
const fractionDigits = isFromAmountField
@@ -208,7 +210,7 @@ const TransferFormAmountInput: React.FC = ({ fieldName }) => {
size='sm'
variant='outlined'
>
- Max
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormDropdown/TransferFormDropdown.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormDropdown/TransferFormDropdown.tsx
index 86b9ceff810b..65e1e37dc34e 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormDropdown/TransferFormDropdown.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferFormDropdown/TransferFormDropdown.tsx
@@ -2,7 +2,9 @@ import React, { RefObject, useCallback, useEffect, useMemo } from 'react';
import { useFormikContext } from 'formik';
import { useHistory } from 'react-router-dom';
import { LegacyChevronDown2pxIcon } from '@deriv/quill-icons';
-import { WalletListCardBadge, WalletText } from '../../../../../../components';
+import { Localize, useTranslations } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { WalletListCardBadge } from '../../../../../../components';
import { useModal } from '../../../../../../components/ModalProvider';
import useDevice from '../../../../../../hooks/useDevice';
import { useTransfer } from '../../provider';
@@ -19,6 +21,7 @@ type TProps = {
const TransferFormDropdown: React.FC
= ({ fieldName, mobileAccountsListRef }) => {
const { setValues, values } = useFormikContext();
const { accounts, activeWallet } = useTransfer();
+ const { localize } = useTranslations();
const { fromAccount, toAccount } = values;
const { isMobile } = useDevice();
const modal = useModal();
@@ -45,7 +48,7 @@ const TransferFormDropdown: React.FC = ({ fieldName, mobileAccountsListR
const selectedAccount = isFromAccountDropdown ? fromAccount : toAccount;
const accountsList = isFromAccountDropdown ? fromAccountList : toAccountList;
- const label = isFromAccountDropdown ? 'Transfer from' : 'Transfer to';
+ const label = isFromAccountDropdown ? localize('Transfer from') : localize('Transfer to');
const { location } = useHistory();
const toAccountLoginId =
location.pathname === '/wallet/account-transfer' ? location.state?.toAccountLoginId : undefined;
@@ -116,6 +119,7 @@ const TransferFormDropdown: React.FC = ({ fieldName, mobileAccountsListR
accountsList={accountsList}
activeWallet={activeWallet}
fromAccount={fromAccount}
+ isFromAccountDropdown={isFromAccountDropdown}
label={label}
onSelect={handleSelect}
selectedAccount={selectedAccount}
@@ -130,7 +134,7 @@ const TransferFormDropdown: React.FC = ({ fieldName, mobileAccountsListR
>
- {label}
+ {label}
{isMobile && }
@@ -139,9 +143,13 @@ const TransferFormDropdown: React.FC
= ({ fieldName, mobileAccountsListR
) : (
-
- Select a trading account{activeWallet?.demo_account === 0 ? ` or a Wallet` : ''}
-
+
+ {activeWallet?.demo_account === 0 ? (
+
+ ) : (
+
+ )}
+
)}
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/TransferMessages.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/TransferMessages.tsx
index e5a6fe8e25d8..01c769af64b1 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/TransferMessages.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/TransferMessages.tsx
@@ -1,10 +1,9 @@
import React, { useEffect } from 'react';
import { useFormikContext } from 'formik';
import { Link } from 'react-router-dom';
-import { Localize } from '@deriv-com/translations';
import { Button } from '@deriv-com/ui';
import { FadedAnimatedList, WalletAlertMessage } from '../../../../../../components';
-import { useTransferMessages } from '../../hooks';
+import useTransferMessages from '../../hooks/useTransferMessages';
import { useTransfer } from '../../provider';
import { TInitialTransferFormValues } from '../../types';
import './TransferMessages.scss';
@@ -30,11 +29,9 @@ const TransferMessages: React.FC = () => {
return (
- {messages.map(({ action, message: { text, values }, type }) => {
- const message = ;
-
+ {messages.map(({ action, message, type }, idx) => {
return (
-
+
{action?.buttonLabel && action?.navigateTo && (
@@ -46,7 +43,7 @@ const TransferMessages: React.FC = () => {
target: '_blank',
})}
>
-
+ {action.buttonLabel}
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/__tests__/TransferMessages.spec.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/__tests__/TransferMessages.spec.tsx
index 939e137828db..23de602a75bd 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/__tests__/TransferMessages.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferMessages/__tests__/TransferMessages.spec.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { useFormikContext } from 'formik';
import { BrowserRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
-import { useTransferMessages } from '../../../hooks';
+import useTransferMessages from '../../../hooks/useTransferMessages';
import { useTransfer } from '../../../provider';
import TransferMessages from '../TransferMessages';
@@ -10,9 +10,7 @@ jest.mock('formik', () => ({
useFormikContext: jest.fn(),
}));
-jest.mock('../../../hooks', () => ({
- useTransferMessages: jest.fn(),
-}));
+jest.mock('../../../hooks/useTransferMessages', () => jest.fn());
jest.mock('../../../provider', () => ({
useTransfer: jest.fn(),
@@ -40,12 +38,12 @@ describe('TransferMessages', () => {
(useTransferMessages as jest.Mock).mockReturnValue([
{
action: { buttonLabel: 'Action', navigateTo: '/action', shouldOpenInNewTab: true },
- message: { text: 'Error message', values: {} },
+ message: 'Error message',
type: 'error',
},
{
action: null,
- message: { text: 'Info message', values: {} },
+ message: 'Info message',
type: 'info',
},
]);
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferReceipt/TransferReceipt.tsx b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferReceipt/TransferReceipt.tsx
index c6b8dfd92f37..c3bf325b9c8b 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/components/TransferReceipt/TransferReceipt.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/components/TransferReceipt/TransferReceipt.tsx
@@ -1,8 +1,9 @@
import React from 'react';
import classNames from 'classnames';
import { LegacyArrowRight2pxIcon } from '@deriv/quill-icons';
-import { Button } from '@deriv-com/ui';
-import { AppCard, WalletCard, WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
+import { AppCard, WalletCard } from '../../../../../../components';
import useDevice from '../../../../../../hooks/useDevice';
import { TPlatforms } from '../../../../../../types';
import { useTransfer } from '../../provider';
@@ -65,7 +66,17 @@ const TransferReceipt = () => {
const transferredAmountMessage = isSameCurrency
? displayTransferredFromAmount
: `${displayTransferredFromAmount} (${displayTransferredToAmount})`;
- const feeMessage = feeAmount ? `Transfer fees: ${feeAmount} ${fromAccount?.currencyConfig?.display_code}` : '';
+ const feeMessage = feeAmount ? (
+
+ ) : (
+ ''
+ );
return (
@@ -90,18 +101,18 @@ const TransferReceipt = () => {
})}
>
-
+
{transferredAmountMessage}
-
+
{Boolean(feeMessage) && (
-
+
{feeMessage}
-
+
)}
-
- Your transfer is successful!
-
+
+
+
{
size={isMobile ? 'md' : 'lg'}
textSize={isMobile ? 'md' : 'sm'}
>
- Make a new transfer
+
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/index.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/index.ts
index 7c2aac9420cd..6528e2633dbf 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/index.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/index.ts
@@ -1,3 +1,2 @@
export { default as useExtendedTransferAccountProperties } from './useExtendedTransferAccountProperties';
export { default as useSortedTransferAccounts } from './useSortedTransferAccounts';
-export { default as useTransferMessages } from './useTransferMessages';
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useSortedTransferAccounts.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useSortedTransferAccounts.ts
index af321374acd1..0c465f20e762 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useSortedTransferAccounts.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useSortedTransferAccounts.ts
@@ -17,11 +17,12 @@ export default useSortedTransferAccounts;
/** A custom hook that sort trading and wallet accounts to display on the screen. */
const sortWalletsAccounts = (a: TAccount, b: TAccount) => {
- if (!a?.accountName || !b?.accountName) return 0;
+ if (!a?.currency || !b?.currency) return 0;
+
if (a.account_type === 'doughflow' && b.account_type === 'doughflow') {
- return a.accountName.localeCompare(b.accountName);
+ return a.currency.localeCompare(b.currency);
} else if (a.account_type === 'crypto' && b.account_type === 'crypto') {
- return a.accountName.localeCompare(b.accountName);
+ return a.currency.localeCompare(b.currency);
} else if (a.account_type === 'doughflow') {
// 'doughflow' comes first
return -1;
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/__tests__/useTransferMessages.spec.tsx b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/__tests__/useTransferMessages.spec.tsx
index f258dd8136ac..feb6d76c9bcf 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/__tests__/useTransferMessages.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/__tests__/useTransferMessages.spec.tsx
@@ -214,13 +214,6 @@ describe('useTransferMessages', () => {
]);
});
- test('should pass values with correct format to messageFns', () => {
- const { result } = renderHook(() => useTransferMessages(mockWalletsTransfer));
-
- expect(result.current[0].message.values.feeMessageText).toEqual('0.1 USD');
- expect(result.current[0].message.values.minimumFeeText).toEqual('0.1 USD');
- });
-
test('should not render transfer messages when active wallet is null', () => {
(useActiveWalletAccount as jest.Mock).mockReturnValueOnce({ data: null });
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/useTransferMessages.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/useTransferMessages.tsx
similarity index 100%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/useTransferMessages.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/useTransferMessages.tsx
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.tsx
similarity index 80%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.tsx
index 9bbad27512bc..f1e1d2eb36f1 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/countLimitsMessageFn.spec.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import countLimitMessageFn from '../countLimitsMessageFn';
describe('countLimitMessageFn', () => {
@@ -27,10 +29,12 @@ describe('countLimitMessageFn', () => {
targetAccount: {},
});
expect(result).toEqual({
- message: {
- text: 'You have reached your daily transfer limit of {{allowedCount}} transfers for your virtual funds. The limit will reset at 00:00 GMT.',
- values: { allowedCount: 10 },
- },
+ message: (
+
+ ),
type: 'error',
});
});
@@ -53,10 +57,12 @@ describe('countLimitMessageFn', () => {
targetAccount: { account_category: 'wallet', accountName: 'Target Wallet' },
});
expect(result).toEqual({
- message: {
- text: 'You have reached your daily transfer limit of {{allowedCount}} transfers between your Wallets. The limit will reset at 00:00 GMT.',
- values: { allowedCount: 5, sourceAccountName: 'Source Wallet', targetAccountName: 'Target Wallet' },
- },
+ message: (
+
+ ),
type: 'error',
});
});
@@ -79,10 +85,16 @@ describe('countLimitMessageFn', () => {
targetAccount: { account_category: 'trading', accountName: 'Target Account' },
});
expect(result).toEqual({
- message: {
- text: 'You have reached your daily transfer limit of {{allowedCount}} transfers between your {{sourceAccountName}} and {{targetAccountName}}. The limit will reset at 00:00 GMT.',
- values: { allowedCount: 5, sourceAccountName: 'Source Account', targetAccountName: 'Target Account' },
- },
+ message: (
+
+ ),
type: 'error',
});
});
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.tsx
similarity index 80%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.tsx
index 32bd8bdfbb21..261e9393e78f 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/cumulativeAccountLimitsMessageFn.spec.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import cumulativeAccountLimitsMessageFn from '../cumulativeAccountLimitsMessageFn';
const mockDisplayMoney = jest.fn((amount, currency, decimals) => `${amount.toFixed(decimals)} ${currency}`);
@@ -57,10 +59,12 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'Your daily transfer limit for virtual funds is {{formattedDemoLimit}}',
- values: { formattedDemoLimit: '1000.00 USD' },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -86,10 +90,12 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'Your remaining daily transfer limit for virtual funds is {{formattedDemoLimit}}.',
- values: { formattedDemoLimit: '500.00 USD' },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -114,14 +120,12 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'The remaining daily transfer limit between your Wallets is {{formattedSourceCurrencyRemainder}}.',
- values: {
- formattedSourceCurrencyRemainder: '800.00 USD',
- sourceAccountName: 'Fiat Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -146,14 +150,16 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'The remaining daily transfer limit between your {{sourceAccountName}} and {{targetAccountName}} is {{formattedSourceCurrencyRemainder}}.',
- values: {
- formattedSourceCurrencyRemainder: '800.00 USD',
- sourceAccountName: 'Trading Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -179,14 +185,12 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'The daily transfer limit between your Wallets is {{formattedSourceCurrencyLimit}}.',
- values: {
- formattedSourceCurrencyLimit: '1000.00 USD',
- sourceAccountName: 'Fiat Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -212,14 +216,16 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'The daily transfer limit between your {{sourceAccountName}} and {{targetAccountName}} is {{formattedSourceCurrencyLimit}}.',
- values: {
- formattedSourceCurrencyLimit: '1000.00 USD',
- sourceAccountName: 'Trading Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -245,14 +251,12 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'You have reached your daily transfer limit of {{formattedSourceCurrencyLimit}} between your Wallets. The limit will reset at 00:00 GMT.',
- values: {
- formattedSourceCurrencyLimit: '1000.00 USD',
- sourceAccountName: 'Fiat Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'error',
});
});
@@ -278,14 +282,16 @@ describe('cumulativeAccountLimitsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'You have reached your daily transfer limit of {{formattedSourceCurrencyLimit}} between your {{sourceAccountName}} and {{targetAccountName}}. The limit will reset at 00:00 GMT.',
- values: {
- formattedSourceCurrencyLimit: '1000.00 USD',
- sourceAccountName: 'Trading Account',
- targetAccountName: 'Fiat Account',
- },
- },
+ message: (
+
+ ),
type: 'error',
});
});
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.tsx
similarity index 81%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.tsx
index 991f91a61f60..693386a5b672 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/insufficientBalanceMessageFn.spec.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import insufficientBalanceMessageFn from '../insufficientBalanceMessageFn';
describe('insufficientBalanceMessageFn', () => {
@@ -33,10 +35,12 @@ describe('insufficientBalanceMessageFn', () => {
} as Parameters[0]);
expect(result).toEqual({
- message: {
- text: 'Your {{sourceAccountName}} has insufficient balance.',
- values: { sourceAccountName: 'USD Wallet' },
- },
+ message: (
+
+ ),
type: 'error',
});
});
@@ -51,10 +55,12 @@ describe('insufficientBalanceMessageFn', () => {
} as Parameters[0]);
expect(result).toEqual({
- message: {
- text: 'Your {{sourceAccountName}} has insufficient balance.',
- values: { sourceAccountName: 'USD Wallet' },
- },
+ message: (
+
+ ),
type: 'error',
});
});
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.tsx
similarity index 74%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.tsx
index 692865a038ec..2b7aae29c719 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/__tests__/lifetimeAccountLimitsBetweenWalletsMessageFn.spec.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import lifetimeAccountLimitsBetweenWalletsMessageFn from '../lifetimeAccountLimitsBetweenWalletsMessageFn';
const mockDisplayMoney = jest.fn((amount, currency, decimals) => `${amount.toFixed(decimals)} ${currency}`);
@@ -54,11 +56,17 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- action: { buttonLabel: 'Verify', navigateTo: '/account/proof-of-identity', shouldOpenInNewTab: true },
- message: {
- text: "You've reached the lifetime transfer limit from your {{sourceAccountName}} to any fiat Wallet. Verify your account to upgrade the limit.",
- values: { sourceAccountName: 'Crypto Account' },
+ action: {
+ buttonLabel: ,
+ navigateTo: '/account/proof-of-identity',
+ shouldOpenInNewTab: true,
},
+ message: (
+
+ ),
type: 'error',
});
});
@@ -82,11 +90,17 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: cryptoAccount,
});
expect(result).toEqual({
- action: { buttonLabel: 'Verify', navigateTo: '/account/proof-of-identity', shouldOpenInNewTab: true },
- message: {
- text: "You've reached the lifetime transfer limit from your {{sourceAccountName}} to any cryptocurrency Wallet. Verify your account to upgrade the limit.",
- values: { sourceAccountName: 'Fiat Account' },
+ action: {
+ buttonLabel: ,
+ navigateTo: '/account/proof-of-identity',
+ shouldOpenInNewTab: true,
},
+ message: (
+
+ ),
type: 'error',
});
});
@@ -111,11 +125,20 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: cryptoAccount,
});
expect(result).toEqual({
- action: { buttonLabel: 'Verify', navigateTo: '/account/proof-of-identity', shouldOpenInNewTab: true },
- message: {
- text: 'Your remaining lifetime transfer limit from {{sourceAccountName}} to any cryptocurrency Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.',
- values: { formattedSourceCurrencyRemainder: '500.00 USD', sourceAccountName: 'Fiat Account' },
+ action: {
+ buttonLabel: ,
+ navigateTo: '/account/proof-of-identity',
+ shouldOpenInNewTab: true,
},
+ message: (
+
+ ),
type: 'success',
});
});
@@ -140,10 +163,12 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: cryptoAccount,
});
expect(result).toEqual({
- message: {
- text: 'The lifetime transfer limit from {{sourceAccountName}} to any cryptocurrency Wallets is up to {{formattedSourceCurrencyLimit}}.',
- values: { formattedSourceCurrencyLimit: '1000.00 USD', sourceAccountName: 'Fiat Account' },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -168,11 +193,20 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- action: { buttonLabel: 'Verify', navigateTo: '/account/proof-of-identity', shouldOpenInNewTab: true },
- message: {
- text: 'Your remaining lifetime transfer limit from {{sourceAccountName}} to any fiat Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.',
- values: { formattedSourceCurrencyRemainder: '5.00000000 BTC', sourceAccountName: 'Crypto Account' },
+ action: {
+ buttonLabel: ,
+ navigateTo: '/account/proof-of-identity',
+ shouldOpenInNewTab: true,
},
+ message: (
+
+ ),
type: 'success',
});
});
@@ -197,10 +231,15 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: fiatAccount,
});
expect(result).toEqual({
- message: {
- text: 'The lifetime transfer limit from {{sourceAccountName}} to any fiat Wallets is up to {{formattedSourceCurrencyLimit}}.',
- values: { formattedSourceCurrencyLimit: '10.00000000 BTC', sourceAccountName: 'Crypto Account' },
- },
+ message: (
+
+ ),
type: 'success',
});
});
@@ -225,11 +264,17 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: cryptoAccount,
});
expect(result).toEqual({
- action: { buttonLabel: 'Verify', navigateTo: '/account/proof-of-identity', shouldOpenInNewTab: true },
- message: {
- text: 'Your remaining lifetime transfer limit between cryptocurrency Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.',
- values: { formattedSourceCurrencyRemainder: '500.00000000 BTC' },
+ action: {
+ buttonLabel: ,
+ navigateTo: '/account/proof-of-identity',
+ shouldOpenInNewTab: true,
},
+ message: (
+
+ ),
type: 'success',
});
});
@@ -254,10 +299,12 @@ describe('lifetimeAccountLimitsBetweenWalletsMessageFn', () => {
targetAccount: cryptoAccount,
});
expect(result).toEqual({
- message: {
- text: 'The lifetime transfer limit between cryptocurrency Wallets is up to {{formattedSourceCurrencyLimit}}.',
- values: { formattedSourceCurrencyLimit: '1000.00000000 BTC' },
- },
+ message: (
+
+ ),
type: 'success',
});
});
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.tsx
similarity index 55%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.tsx
index 1823a8fea33d..d60b82d54c7e 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/countLimitsMessageFn.tsx
@@ -1,8 +1,8 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { TMessageFnProps, TTransferMessage } from '../../../types';
-let text: TTransferMessage['message']['text'],
- type: TTransferMessage['type'],
- values: TTransferMessage['message']['values'];
+let message: TTransferMessage['message'], type: TTransferMessage['type'];
const countLimitMessageFn = ({ activeWallet, limits, sourceAccount, targetAccount }: TMessageFnProps) => {
if (!targetAccount) return null;
@@ -32,32 +32,40 @@ const countLimitMessageFn = ({ activeWallet, limits, sourceAccount, targetAccoun
if (allowedCount === undefined || availableCount === undefined) return null;
if (availableCount === 0 && isDemoTransfer) {
- text =
- 'You have reached your daily transfer limit of {{allowedCount}} transfers for your virtual funds. The limit will reset at 00:00 GMT.';
- values = {
- allowedCount,
- };
+ message = (
+
+ );
type = 'error' as const;
return {
- message: { text, values },
+ message,
type,
};
}
if (availableCount === 0) {
- text = isTransferBetweenWallets
- ? 'You have reached your daily transfer limit of {{allowedCount}} transfers between your Wallets. The limit will reset at 00:00 GMT.'
- : 'You have reached your daily transfer limit of {{allowedCount}} transfers between your {{sourceAccountName}} and {{targetAccountName}}. The limit will reset at 00:00 GMT.';
- values = {
- allowedCount,
- sourceAccountName: sourceAccount.accountName,
- targetAccountName: targetAccount.accountName,
- };
+ message = isTransferBetweenWallets ? (
+
+ ) : (
+
+ );
type = 'error' as const;
return {
- message: { text, values },
+ message,
type,
};
}
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.tsx
similarity index 57%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.tsx
index bf424d7e7323..7cac0b7c83a8 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/cumulativeAccountLimitsMessageFn.tsx
@@ -1,6 +1,8 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { TMessageFnProps, TTransferMessage } from '../../../types';
-let text: TTransferMessage['message']['text'], values: TTransferMessage['message']['values'];
+let message: TTransferMessage['message'];
const cumulativeAccountLimitsMessageFn = ({
activeWallet,
@@ -53,20 +55,28 @@ const cumulativeAccountLimitsMessageFn = ({
if (isDemoTransfer) {
if (allowedSumUSD === availableSumUSD) {
- text = 'Your daily transfer limit for virtual funds is {{formattedDemoLimit}}';
- values = { formattedDemoLimit };
+ message = (
+
+ );
return {
- message: { text, values },
+ message,
type: 'success' as const,
};
}
- text = 'Your remaining daily transfer limit for virtual funds is {{formattedDemoLimit}}.';
- values = { formattedDemoLimit };
+ message = (
+
+ );
return {
- message: { text, values },
+ message,
type: 'success' as const,
};
}
@@ -105,48 +115,69 @@ const cumulativeAccountLimitsMessageFn = ({
);
if (availableSumUSD === 0) {
- text = isTransferBetweenWallets
- ? 'You have reached your daily transfer limit of {{formattedSourceCurrencyLimit}} between your Wallets. The limit will reset at 00:00 GMT.'
- : 'You have reached your daily transfer limit of {{formattedSourceCurrencyLimit}} between your {{sourceAccountName}} and {{targetAccountName}}. The limit will reset at 00:00 GMT.';
- values = {
- formattedSourceCurrencyLimit,
- sourceAccountName: sourceAccount.accountName,
- targetAccountName: targetAccount.accountName,
- };
+ message = isTransferBetweenWallets ? (
+
+ ) : (
+
+ );
return {
- message: { text, values },
+ message,
type: 'error' as const,
};
}
if (allowedSumUSD === availableSumUSD) {
- text = isTransferBetweenWallets
- ? 'The daily transfer limit between your Wallets is {{formattedSourceCurrencyLimit}}.'
- : 'The daily transfer limit between your {{sourceAccountName}} and {{targetAccountName}} is {{formattedSourceCurrencyLimit}}.';
- values = {
- formattedSourceCurrencyLimit,
- sourceAccountName: sourceAccount.accountName,
- targetAccountName: targetAccount.accountName,
- };
+ message = isTransferBetweenWallets ? (
+
+ ) : (
+
+ );
return {
- message: { text, values },
+ message,
type: sourceAmount > sourceCurrencyRemainder ? ('error' as const) : ('success' as const),
};
}
- text = isTransferBetweenWallets
- ? 'The remaining daily transfer limit between your Wallets is {{formattedSourceCurrencyRemainder}}.'
- : 'The remaining daily transfer limit between your {{sourceAccountName}} and {{targetAccountName}} is {{formattedSourceCurrencyRemainder}}.';
- values = {
- formattedSourceCurrencyRemainder,
- sourceAccountName: sourceAccount.accountName,
- targetAccountName: targetAccount.accountName,
- };
+ message = isTransferBetweenWallets ? (
+
+ ) : (
+
+ );
return {
- message: { text, values },
+ message,
type: sourceAmount > sourceCurrencyRemainder ? ('error' as const) : ('success' as const),
};
};
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.tsx
similarity index 62%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.tsx
index efed851e510b..83a51a5d0901 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/insufficientBalanceMessageFn.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { TMessageFnProps } from '../../../types';
const insufficientBalanceMessageFn = ({ sourceAccount, sourceAmount }: TMessageFnProps) => {
@@ -6,10 +8,13 @@ const insufficientBalanceMessageFn = ({ sourceAccount, sourceAmount }: TMessageF
const sourceAccountBalance = Number(sourceAccount.balance);
if (sourceAccountBalance === 0 || sourceAccountBalance < sourceAmount) {
- const message = {
- text: 'Your {{sourceAccountName}} has insufficient balance.',
- values: { sourceAccountName: sourceAccount.accountName },
- };
+ const message = (
+
+ );
+
return {
message,
type: 'error' as const,
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.tsx
similarity index 53%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.tsx
index 3c3eefa482b4..98c99d86aef9 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/lifetimeAccountLimitsBetweenWalletsMessageFn.tsx
@@ -1,9 +1,11 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { TMessageFnProps, TTransferMessage } from '../../../types';
-let text: TTransferMessage['message']['text'], values: TTransferMessage['message']['values'];
+let message: TTransferMessage['message'];
const verifyPOIAction = {
- buttonLabel: 'Verify',
+ buttonLabel: ,
navigateTo: '/account/proof-of-identity',
shouldOpenInNewTab: true,
};
@@ -70,15 +72,22 @@ const lifetimeAccountLimitsBetweenWalletsMessageFn = ({
);
if (availableSumActiveWalletCurrency === 0) {
- text =
- targetWalletType === 'crypto'
- ? "You've reached the lifetime transfer limit from your {{sourceAccountName}} to any cryptocurrency Wallet. Verify your account to upgrade the limit."
- : "You've reached the lifetime transfer limit from your {{sourceAccountName}} to any fiat Wallet. Verify your account to upgrade the limit.";
- values = { sourceAccountName: sourceAccount.accountName };
+ message =
+ targetWalletType === 'crypto' ? (
+
+ ) : (
+
+ );
return {
action: verifyPOIAction,
- message: { text, values },
+ message,
type: 'error' as const,
};
}
@@ -87,23 +96,33 @@ const lifetimeAccountLimitsBetweenWalletsMessageFn = ({
switch (limitsCaseKey) {
case 'fiat_to_crypto':
case 'crypto_to_fiat':
- text =
- targetWalletType === 'crypto'
- ? 'The lifetime transfer limit from {{sourceAccountName}} to any cryptocurrency Wallets is up to {{formattedSourceCurrencyLimit}}.'
- : 'The lifetime transfer limit from {{sourceAccountName}} to any fiat Wallets is up to {{formattedSourceCurrencyLimit}}.';
- values = { formattedSourceCurrencyLimit, sourceAccountName: sourceAccount.accountName };
+ message =
+ targetWalletType === 'crypto' ? (
+
+ ) : (
+
+ );
return {
- message: { text, values },
+ message,
type: 'success' as const,
};
case 'crypto_to_crypto':
- text =
- 'The lifetime transfer limit between cryptocurrency Wallets is up to {{formattedSourceCurrencyLimit}}.';
- values = { formattedSourceCurrencyLimit };
+ message = (
+
+ );
return {
- message: { text, values },
+ message,
type: 'success' as const,
};
default:
@@ -113,25 +132,35 @@ const lifetimeAccountLimitsBetweenWalletsMessageFn = ({
switch (limitsCaseKey) {
case 'fiat_to_crypto':
case 'crypto_to_fiat':
- text =
- targetWalletType === 'crypto'
- ? 'Your remaining lifetime transfer limit from {{sourceAccountName}} to any cryptocurrency Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.'
- : 'Your remaining lifetime transfer limit from {{sourceAccountName}} to any fiat Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.';
- values = { formattedSourceCurrencyRemainder, sourceAccountName: sourceAccount.accountName };
+ message =
+ targetWalletType === 'crypto' ? (
+
+ ) : (
+
+ );
return {
action: verifyPOIAction,
- message: { text, values },
+ message,
type: 'success' as const,
};
case 'crypto_to_crypto':
- text =
- 'Your remaining lifetime transfer limit between cryptocurrency Wallets is {{formattedSourceCurrencyRemainder}}. Verify your account to upgrade the limit.';
- values = { formattedSourceCurrencyRemainder };
+ message = (
+
+ );
return {
action: verifyPOIAction,
- message: { text, values },
+ message,
type: 'success' as const,
};
default:
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.ts b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.tsx
similarity index 57%
rename from packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.ts
rename to packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.tsx
index 45cda1e0040a..b67cddc45209 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/hooks/useTransferMessages/utils/transferFeesBetweenWalletsMessageFn.tsx
@@ -1,3 +1,5 @@
+import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { TMessageFnProps } from '../../../types';
const transferFeesBetweenWalletsMessageFn = ({
@@ -37,18 +39,29 @@ const transferFeesBetweenWalletsMessageFn = ({
sourceAccount.currencyConfig.fractional_digits
);
- const text =
- 'Fee: {{feeMessageText}} ({{feePercentage}}% transfer fee or {{minimumFeeText}}, whichever is higher, applies for fund transfers between your {{fiatAccountName}}{{conjunction}} cryptocurrency Wallets)';
- const values = {
- conjunction: isTransferBetweenCryptoWallets ? '' : ' Wallet and ',
- feeMessageText,
- feePercentage,
- fiatAccountName: isTransferBetweenCryptoWallets ? '' : fiatAccount?.wallet_currency_type,
- minimumFeeText,
- };
+ const message = isTransferBetweenCryptoWallets ? (
+
+ ) : (
+
+ );
return {
- message: { text, values },
+ message,
type: 'info' as const,
};
};
diff --git a/packages/wallets/src/features/cashier/modules/Transfer/types/types.ts b/packages/wallets/src/features/cashier/modules/Transfer/types/types.ts
index c8f27651e1f5..9c3f8dbdff1b 100644
--- a/packages/wallets/src/features/cashier/modules/Transfer/types/types.ts
+++ b/packages/wallets/src/features/cashier/modules/Transfer/types/types.ts
@@ -15,19 +15,14 @@ export type TInitialTransferFormValues = {
};
type TAction = {
- buttonLabel?: string;
+ buttonLabel?: JSX.Element;
navigateTo?: string;
shouldOpenInNewTab?: boolean;
};
-type TMessage = {
- text: string;
- values: Record;
-};
-
export type TTransferMessage = {
action?: TAction;
- message: TMessage;
+ message: JSX.Element;
type: 'error' | 'info' | 'success';
};
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/WithdrawalCrypto.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/WithdrawalCrypto.tsx
index 02db5008d9e4..0a3bbd08b882 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/WithdrawalCrypto.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/WithdrawalCrypto.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { Loader } from '@deriv-com/ui';
-import { WalletText } from '../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Loader, Text } from '@deriv-com/ui';
import { isServerError } from '../../../../utils/utils';
import { WithdrawalErrorScreen } from '../../screens';
import { TransactionStatus } from '../TransactionStatus';
@@ -22,6 +22,7 @@ const WithdrawalCrypto: React.FC setVerificationCode('');
+ const currency = activeWallet?.currency;
const resetError = () => {
onCloseHandler();
@@ -42,10 +43,12 @@ const WithdrawalCrypto: React.FC
-
- Withdraw {activeWallet?.currency ? getCurrencyConfig(activeWallet?.currency)?.name : ''} (
- {activeWallet?.currency}) to your wallet
-
+
+
+
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoDisclaimer/WithdrawalCryptoDisclaimer.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoDisclaimer/WithdrawalCryptoDisclaimer.tsx
index 9a11913d8851..cb859e6fee7a 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoDisclaimer/WithdrawalCryptoDisclaimer.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoDisclaimer/WithdrawalCryptoDisclaimer.tsx
@@ -1,22 +1,27 @@
import React from 'react';
+import { Localize } from '@deriv-com/translations';
import { InlineMessage } from '../../../../../../components';
import './WithdrawalCryptoDisclaimer.scss';
-const WithdrawalDisclaimer = () => (
+const WithdrawalCryptoDisclaimer = () => (
- Do not enter an address linked to an initial coin offering (ICO) purchase or crowdsale. If you do,
- the initial coin offering (ICO) tokens will not be credited into your account.
+
- Please note that your maximum and minimum withdrawal limits aren't fixed. They change due to
- the high volatility of cryptocurrency.
+
);
-export default WithdrawalDisclaimer;
+export default WithdrawalCryptoDisclaimer;
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/WithdrawalCryptoForm.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/WithdrawalCryptoForm.tsx
index 0db0ed237736..65929959fc9e 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/WithdrawalCryptoForm.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/WithdrawalCryptoForm.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Field, FieldProps, Formik } from 'formik';
import { useGrowthbookIsOn } from '@deriv/api-v2';
+import { Localize, useTranslations } from '@deriv-com/translations';
import { Button } from '@deriv-com/ui';
import { WalletTextField } from '../../../../../../components';
import { useWithdrawalCryptoContext } from '../../provider';
@@ -13,6 +14,7 @@ import './WithdrawalCryptoForm.scss';
const WithdrawalCryptoForm: React.FC = () => {
const { activeWallet, cryptoEstimationsFeeUniqueId, fractionalDigits, requestCryptoWithdrawal } =
useWithdrawalCryptoContext();
+ const { localize } = useTranslations();
const [isPriorityCryptoWithdrawalEnabled] = useGrowthbookIsOn({
featureFlag: 'priority_crypto_withdrawal',
@@ -45,7 +47,9 @@ const WithdrawalCryptoForm: React.FC = () => {
data-testid='dt_withdrawal_crypto_address_input'
errorMessage={meta.touched && errors.cryptoAddress}
isInvalid={meta.touched && Boolean(errors?.cryptoAddress)}
- label={`Your ${activeWallet?.currency_config?.name} cryptocurrency wallet address`}
+ label={localize('Your {{currencyName}} cryptocurrency wallet address', {
+ currencyName: activeWallet?.currency_config?.name,
+ })}
onChange={event => {
setFieldValue(field.name, event.target.value, true);
setFieldTouched(field.name, true);
@@ -68,7 +72,7 @@ const WithdrawalCryptoForm: React.FC = () => {
textSize='md'
type='submit'
>
- Withdraw
+
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPercentageSelector/WithdrawalCryptoPercentageSelector.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPercentageSelector/WithdrawalCryptoPercentageSelector.tsx
index 1f5acd375478..97084a996f3c 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPercentageSelector/WithdrawalCryptoPercentageSelector.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPercentageSelector/WithdrawalCryptoPercentageSelector.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { displayMoney } from '@deriv/api-v2/src/utils';
-import { WalletsPercentageSelector, WalletText } from '../../../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { WalletsPercentageSelector } from '../../../../../../../../components';
import useAllBalanceSubscription from '../../../../../../../../hooks/useAllBalanceSubscription';
import { useWithdrawalCryptoContext } from '../../../../provider';
import { TWithdrawalForm } from '../../../../types';
@@ -29,7 +31,12 @@ const WithdrawalCryptoPercentageSelector: React.FC = () => {
if (amount <= activeWalletBalance) {
const percentage = Math.round((amount * 100) / activeWalletBalance);
- return `${percentage}% of available balance (${activeWalletDisplayBalance})`;
+ return (
+
+ );
}
};
@@ -86,9 +93,9 @@ const WithdrawalCryptoPercentageSelector: React.FC = () => {
}}
/>
-
+
{isValidInput && getPercentageMessage(values.cryptoAmount)}
-
+
);
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriority/WithdrawalCryptoPriority.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriority/WithdrawalCryptoPriority.tsx
index 86427e7588d8..6a04306eebe8 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriority/WithdrawalCryptoPriority.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriority/WithdrawalCryptoPriority.tsx
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { useFormikContext } from 'formik';
+import { useTranslations } from '@deriv-com/translations';
import { Tooltip } from '@deriv-com/ui';
import { WalletCheckbox, WalletsPriorityCryptoWithdrawLoader } from '../../../../../../../../components';
import InfoIcon from '../../../../../../../../public/images/ic-info-outline.svg';
@@ -8,6 +9,8 @@ import { WithdrawalCryptoPriorityFeeInfo } from '../WithdrawalCryptoPriorityFeeI
import './WithdrawalCryptoPriority.scss';
const WithdrawalCryptoPriority = () => {
+ const { localize } = useTranslations();
+
const { handleChange, values } = useFormikContext<{
cryptoAmount: string;
priorityWithdrawal: boolean;
@@ -42,7 +45,7 @@ const WithdrawalCryptoPriority = () => {
{
@@ -59,7 +62,9 @@ const WithdrawalCryptoPriority = () => {
/>
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriorityFeeInfo/WithdrawalCryptoPriorityFeeInfo.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriorityFeeInfo/WithdrawalCryptoPriorityFeeInfo.tsx
index b587f0ebc7ab..a1e29b7d2267 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriorityFeeInfo/WithdrawalCryptoPriorityFeeInfo.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoPriorityFeeInfo/WithdrawalCryptoPriorityFeeInfo.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { Tooltip } from '@deriv-com/ui';
-import { WalletText } from '../../../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text, Tooltip } from '@deriv-com/ui';
import { useWithdrawalCryptoContext } from '../../../../provider';
import './WithdrawalCryptoPriorityFeeInfo.scss';
@@ -11,35 +11,38 @@ const WithdrawalCryptoPriorityFeeInfo = ({ cryptoAmount }: { cryptoAmount: strin
return (
-
- Withdrawal amount:
-
-
+
+
+
+
{Number(cryptoAmount).toFixed(fractionalDigits.crypto as number)} {activeWallet?.currency}
-
+
-
- Transaction fee
-
- ({countDownEstimationFee}s)
-
+
+
+
+
+
:
-
+
-
+
{cryptoEstimationsFee.toFixed(fractionalDigits.crypto as number)} {activeWallet?.currency}
-
+
-
- Amount received:
-
-
+
+
+
+
{Number(cryptoAmount) > 0
? (
parseFloat(Number(cryptoAmount).toFixed(fractionalDigits.crypto as number)) -
@@ -47,7 +50,7 @@ const WithdrawalCryptoPriorityFeeInfo = ({ cryptoAmount }: { cryptoAmount: strin
).toFixed(fractionalDigits.crypto as number)
: Number(cryptoAmount).toFixed(fractionalDigits.crypto as number)}{' '}
{activeWallet?.currency}
-
+
);
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/WithdrawalCryptoReceipt.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/WithdrawalCryptoReceipt.tsx
index 8d7601ca2d85..b0fab2ed475c 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/WithdrawalCryptoReceipt.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/WithdrawalCryptoReceipt.tsx
@@ -1,8 +1,9 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { LegacyArrowDown2pxIcon } from '@deriv/quill-icons';
-import { Button } from '@deriv-com/ui';
-import { WalletCard, WalletText } from '../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
+import { WalletCard } from '../../../../../../components';
import { TWithdrawalReceipt } from '../../types';
import { WithdrawalCryptoDestinationAddress } from './components';
import './WithdrawalCryptoReceipt.scss';
@@ -25,23 +26,28 @@ const WithdrawalCryptoReceipt: React.FC = ({ onClose, withdrawalReceipt
-
- Amount received
-
-
+
+
+
+
{transactionFee ? amountReceived : amount} {currency}
-
+
{transactionFee && (
-
- (Transaction fee: {transactionFee} {currency})
-
+
+
+
)}
-
- Your withdrawal is currently in review. It will be processed within 24 hours. We’ll send you
- an email once your transaction has been processed.
-
+
+
+
= ({ onClose, withdrawalReceipt
textSize='md'
variant='outlined'
>
- View transactions
+
-
- Close
+
+
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/__tests__/WithdrawalCryptoReceipt.spec.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/__tests__/WithdrawalCryptoReceipt.spec.tsx
index ae70e3465b82..1ba26802b39c 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/__tests__/WithdrawalCryptoReceipt.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/__tests__/WithdrawalCryptoReceipt.spec.tsx
@@ -36,7 +36,7 @@ describe('WithdrawalCryptoReceipt', () => {
expect(addressElement).toBeInTheDocument();
const reviewTextElement = screen.getByText(
- 'Your withdrawal is currently in review. It will be processed within 24 hours. We’ll send you an email once your transaction has been processed.'
+ "Your withdrawal is currently in review. It will be processed within 24 hours. We'll send you an email once your transaction has been processed."
);
expect(reviewTextElement).toBeInTheDocument();
});
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/components/WithdrawalCryptoDestinationAddress/WithdrawalCryptoDestinationAddress.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/components/WithdrawalCryptoDestinationAddress/WithdrawalCryptoDestinationAddress.tsx
index 7a1770088a2b..623790fe3147 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/components/WithdrawalCryptoDestinationAddress/WithdrawalCryptoDestinationAddress.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/components/WithdrawalCryptoDestinationAddress/WithdrawalCryptoDestinationAddress.tsx
@@ -1,19 +1,21 @@
import React from 'react';
-import { WalletClipboard, WalletText } from '../../../../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Text } from '@deriv-com/ui';
+import { WalletClipboard } from '../../../../../../../../components';
import './WithdrawalCryptoDestinationAddress.scss';
const WithdrawalCryptoDestinationAddress: React.FC<{ address?: string }> = ({ address }) => {
return (
-
- Destination address
-
+
+
+
-
+
{address}
-
+
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/utils/withdrawalCryptoValidators.ts b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/utils/withdrawalCryptoValidators.ts
index f7e1c422f381..4ac2eb7fffe9 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/utils/withdrawalCryptoValidators.ts
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalCrypto/utils/withdrawalCryptoValidators.ts
@@ -1,14 +1,17 @@
+import { localize } from '@deriv-com/translations';
import { TWithdrawalCryptoContext } from '../provider';
const helperMessageMapper = {
balanceLessThanMinWithdrawalLimit: (balance: string, min: string) =>
- `Your balance (${balance}) is less than the current minimum withdrawal allowed (${min}). Please top up your wallet to continue with your withdrawal.`,
- decimalPlacesExceeded: (limit: number) => `Up to ${limit} decimal places are allowed.`,
- fieldRequired: 'This field is required.',
- insufficientFunds: 'Insufficient funds',
- invalidInput: 'Should be a valid number.',
+ localize(
+ `Your balance (${balance}) is less than the current minimum withdrawal allowed (${min}). Please top up your wallet to continue with your withdrawal.`
+ ),
+ decimalPlacesExceeded: (limit: number) => localize(`Up to ${limit} decimal places are allowed.`),
+ fieldRequired: localize('This field is required.'),
+ insufficientFunds: localize('Insufficient funds'),
+ invalidInput: localize('Should be a valid number.'),
withdrawalLimitError: (min: string, max: string) => {
- return `The current allowed withdraw amount is ${min} to ${max}.`;
+ return localize(`The current allowed withdraw amount is ${min} to ${max}.`);
},
};
@@ -19,7 +22,7 @@ const validateCryptoAddress = (address: string) => {
if (!address) return helperMessageMapper.fieldRequired;
if (address.length < MIN_ADDRESS_LENGTH || address.length > MAX_ADDRESS_LENGTH) {
- return 'Your wallet address should have 25 to 64 characters.';
+ return localize('Your wallet address should have 25 to 64 characters.');
}
return undefined;
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalLocked/__tests__/WithdrawalLocked.spec.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalLocked/__tests__/WithdrawalLocked.spec.tsx
index fc5d87fa2032..120ad9fa9c0b 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalLocked/__tests__/WithdrawalLocked.spec.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalLocked/__tests__/WithdrawalLocked.spec.tsx
@@ -22,6 +22,7 @@ jest.mock('@deriv/api-v2', () => ({
}));
jest.mock('@deriv-com/ui', () => ({
+ ...jest.requireActual('@deriv-com/ui'),
Loader: jest.fn(() => Loading...
),
}));
diff --git a/packages/wallets/src/features/cashier/modules/WithdrawalVerification/WithdrawalVerificationRequest/WithdrawalVerificationRequest.tsx b/packages/wallets/src/features/cashier/modules/WithdrawalVerification/WithdrawalVerificationRequest/WithdrawalVerificationRequest.tsx
index f9da3c5420c8..c70c5e874fe6 100644
--- a/packages/wallets/src/features/cashier/modules/WithdrawalVerification/WithdrawalVerificationRequest/WithdrawalVerificationRequest.tsx
+++ b/packages/wallets/src/features/cashier/modules/WithdrawalVerification/WithdrawalVerificationRequest/WithdrawalVerificationRequest.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import { DerivLightEmailVerificationIcon } from '@deriv/quill-icons';
-import { Button } from '@deriv-com/ui';
-import { WalletsActionScreen, WalletText } from '../../../../../components';
+import { Localize } from '@deriv-com/translations';
+import { Button, Text } from '@deriv-com/ui';
+import { WalletsActionScreen } from '../../../../../components';
import './WithdrawalVerificationRequest.scss';
type TProps = {
@@ -14,12 +15,12 @@ const WithdrawalVerificationRequest: React.FC = ({ sendEmail }) => {
-
- Press the button below, and we'll email you a verification link.
-
-
- This is to confirm that it's you making the withdrawal request.
-
+
+
+
+
+
+