diff --git a/.husky/pre-commit b/.husky/pre-commit index 146c0dfd0def..4bf2021b3d1d 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged --allow-empty +npx lint-staged --allow-empty \ No newline at end of file diff --git a/packages/account/build/webpack.config.js b/packages/account/build/webpack.config.js index e28c13beb8af..bdc99ad6edfd 100644 --- a/packages/account/build/webpack.config.js +++ b/packages/account/build/webpack.config.js @@ -22,7 +22,7 @@ module.exports = function (env) { 'demo-message': 'Components/demo-message', 'error-component': 'Components/error-component', 'file-uploader-container': 'Components/file-uploader-container', - 'financial-assessment': 'Sections/Profile/FinancialAssessment', + 'financial-assessment': 'Sections/Assessment/FinancialAssessment', 'financial-details': 'Components/financial-details', 'financial-details-config': 'Configs/financial-details-config', 'form-body': 'Components/form-body', @@ -31,34 +31,40 @@ module.exports = function (env) { 'icon-message-content': 'Components/icon-message-content', 'leave-confirm': 'Components/leave-confirm', 'load-error-message': 'Components/load-error-message', + 'personal-details': 'Components/personal-details', + 'personal-details-config': 'Configs/personal-details-config', 'poa-expired': 'Components/poa/status/expired', 'poa-needs-review': 'Components/poa/status/needs-review', 'poa-status-codes': 'Components/poa/status/status-codes', 'poa-submitted': 'Components/poa/status/submitted', 'poa-unverified': 'Components/poa/status/unverified', 'poa-verified': 'Components/poa/status/verified', - 'personal-details': 'Components/personal-details', - 'personal-details-config': 'Configs/personal-details-config', 'poi-expired': 'Components/poi/status/expired', 'poi-missing-personal-details': 'Components/poi/missing-personal-details', 'poi-unsupported': 'Components/poi/status/unsupported', 'poi-unverified': 'Components/poi/status/unverified', 'poi-upload-complete': 'Components/poi/status/upload-complete', 'poi-verified': 'Components/poi/status/verified', + 'proof-of-address-container': 'Sections/Verification/ProofOfAddress/proof-of-address-container.jsx', 'proof-of-identity': 'Sections/Verification/ProofOfIdentity/proof-of-identity.jsx', 'proof-of-identity-container': 'Sections/Verification/ProofOfIdentity/proof-of-identity-container.jsx', - 'proof-of-address-container': 'Sections/Verification/ProofOfAddress/proof-of-address-container.jsx', + 'proof-of-identity-config': 'Configs/proof-of-identity-config', + 'proof-of-identity-form-on-signup': 'Components/poi/poi-form-on-signup', + 'proof-of-identity-container-for-mt5': + 'Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5', + 'poi-poa-docs-submitted': 'Components/poi-poa-docs-submitted/poi-poa-docs-submitted.jsx', 'reset-trading-password-modal': 'Components/reset-trading-password-modal', + 'risk-tolerance-warning-modal': 'Components/trading-assessment/risk-tolerance-warning-modal.jsx', 'self-exclusion': 'Components/self-exclusion', 'scrollbars-container': 'Components/scrollbars-container', 'sent-email-modal': 'Components/sent-email-modal', 'terms-of-use': 'Components/terms-of-use', 'terms-of-use-config': 'Configs/terms-of-use-config', - 'proof-of-identity-config': 'Configs/proof-of-identity-config', - 'proof-of-identity-form-on-signup': 'Components/poi/poi-form-on-signup', - 'proof-of-identity-container-for-mt5': - 'Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5', - 'poi-poa-docs-submitted': 'Components/poi-poa-docs-submitted/poi-poa-docs-submitted.jsx', + 'trading-assessment': 'Sections/Assessment/TradingAssessment', + 'trading-assessment-config': 'Configs/trading-assessment-config', + 'trading-assessment-new-user': 'Components/trading-assessment/trading-assessment-new-user.jsx', + 'test-warning-modal': 'Components/trading-assessment/test-warning-modal.jsx', + 'trading-assessment-form': 'Components/trading-assessment/trading-assessment-form.jsx', }, mode: IS_RELEASE ? 'production' : 'development', module: { diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index aac707303ff6..c5e9211e9318 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -21,8 +21,10 @@ import { import { Link } from 'react-router-dom'; import { localize, Localize } from '@deriv/translations'; import { getLegalEntityName, isDesktop, isMobile, routes, toMoment, PlatformContext } from '@deriv/shared'; +import { getEmploymentStatusList } from 'Sections/Assessment/FinancialAssessment/financial-information-list'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; import FormSubHeader from '../form-sub-header'; +import classNames from 'classnames'; const DateOfBirthField = props => ( @@ -161,7 +163,7 @@ const PersonalDetails = ({ onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} > - {({ handleSubmit, errors, setFieldValue, touched, values, handleChange, handleBlur }) => ( + {({ handleSubmit, errors, setFieldValue, setFieldTouched, touched, values, handleChange, handleBlur }) => ( {({ setRef, height }) => (
)} + {'employment_status' in props.value && ( +
+ + + + + { + setFieldTouched('employment_status', true); + handleChange(e); + }} + /> + +
+ )} {'tax_identification_confirm' in props.value && ( { + return ( + + + + + + {body_content} + + + + {current_question_details.current_question_index + 1} {localize('of')}{' '} + {assessment_questions.length} + + + +
+ +
+ + {({ setFieldValue, values }) => { + const { question_text, form_control, answer_options, questions } = + current_question_details.current_question; + + return ( + +
+ {questions?.length ? ( + + ) : ( + handleValueSelection(e, form_control, setFieldValue, values)} + values={values} + form_control={form_control} + setEnableNextSection={setIsNextButtonEnabled} + /> + )} +
+ + {is_header_navigation ? ( + onCancel(values)} + type='button' + onClick={() => onSubmit(values)} + /> + ) : ( + + {current_question_details.current_question_index !== 0 && ( +
+
+ ); +}; + +export default TradingAssessmentForm; diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-new-user.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-new-user.jsx new file mode 100644 index 000000000000..6554d3bcf987 --- /dev/null +++ b/packages/account/src/Components/trading-assessment/trading-assessment-new-user.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import TradingAssessmentForm from './trading-assessment-form'; + +const TradingAssessmentNewUser = ({ + assessment_questions, + goToNextStep, + goToPreviousStep, + onSave, + onCancel, + onSubmit, + getCurrentStep, + value, +}) => { + const handleCancel = values => { + const current_step = getCurrentStep() - 1; + onSave(current_step, values); + onCancel(current_step, goToPreviousStep); + }; + + const handleSubmit = (values, actions, should_override) => { + let process_form_values = { ...values }; + if (should_override) { + // Remove the keys with no values + process_form_values = Object.entries(process_form_values).reduce((accumulator, [key, val]) => { + if (val) { + return { ...accumulator, [key]: val }; + } + return { ...accumulator }; + }, {}); + } + onSubmit(getCurrentStep() - 1, process_form_values, null, goToNextStep, should_override); + }; + + return ( + + ); +}; + +export default TradingAssessmentNewUser; diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx new file mode 100644 index 000000000000..bdc9551ba43b --- /dev/null +++ b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Field } from 'formik'; +import { Text, RadioGroup } from '@deriv/components'; + +const TradingAssessmentRadioButton = ({ text, list, onChange, values, form_control, setEnableNextSection }) => { + React.useEffect(() => { + setEnableNextSection(!!values[form_control]); + }, [form_control]); + + return ( +
+ + {text} + + + {() => ( + { + onChange(e); + setEnableNextSection(true); + }} + > + {list.map(answer => ( + + ))} + + )} + +
+ ); +}; + +export default TradingAssessmentRadioButton; diff --git a/packages/account/src/Configs/personal-details-config.js b/packages/account/src/Configs/personal-details-config.js index def7e632a22b..a963fac0bf32 100644 --- a/packages/account/src/Configs/personal-details-config.js +++ b/packages/account/src/Configs/personal-details-config.js @@ -134,6 +134,11 @@ const personal_details_config = ({ residence_list, account_settings, is_appstore ], ], }, + employment_status: { + default_value: '', + supported_in: ['maltainvest'], + rules: [['req', localize('Employment status is required.')]], + }, tax_identification_confirm: { default_value: false, supported_in: ['maltainvest'], diff --git a/packages/account/src/Configs/trading-assessment-config.js b/packages/account/src/Configs/trading-assessment-config.js new file mode 100644 index 000000000000..d3502e946354 --- /dev/null +++ b/packages/account/src/Configs/trading-assessment-config.js @@ -0,0 +1,308 @@ +import { generateValidationFunction, getDefaultFields } from '@deriv/shared'; +import { localize } from '@deriv/translations'; + +export const trading_assessment_questions = [ + { + question_text: localize( + 'Do you understand that you could potentially lose 100% of the money you use to trade?' + ), + section: 'risk_tolerance', + answer_options: [ + { text: localize('Yes'), value: 'Yes' }, + { text: localize('No'), value: 'No' }, + ], + form_control: 'risk_tolerance', + field_type: 'radio', + }, + { + question_text: localize('How much knowledge and experience do you have in relation to online trading?'), + section: 'source_of_experience', + form_control: 'source_of_experience', + answer_options: [ + { + text: localize( + 'I have an academic degree, professional certification, and/or work experience related to financial services.' + ), + value: 'I have an academic degree, professional certification, and/or work experience.', + }, + { + text: localize( + 'I trade forex CFDs and other complex financial instruments regularly on other platforms.' + ), + value: 'I trade forex CFDs and other complex financial instruments.', + }, + { + text: localize('I have attended seminars, training, and/or workshops related to trading.'), + value: 'I have attended seminars, training, and/or workshops.', + }, + { + text: localize('I am interested in trading but have very little experience.'), + value: 'I have little experience.', + }, + { + text: localize('I have no knowledge and experience in trading at all.'), + value: 'I have no knowledge.', + }, + ], + field_type: 'radio', + }, + { + section: 'trading_experience', + questions: [ + { + question_text: localize('How much experience do you have in CFD trading?'), + field_type: 'dropdown', + form_control: 'cfd_experience', + answer_options: [ + { + text: localize('No experience'), + value: 'No experience', + }, + { + text: localize('Less than a year'), + value: 'Less than a year', + }, + { + text: localize('1 - 2 years'), + value: '1 - 2 years', + }, + { + text: localize('Over 3 years'), + value: 'Over 3 years', + }, + ], + }, + { + question_text: localize('How many CFD trades have you placed in the past 12 months?'), + field_type: 'dropdown', + form_control: 'cfd_frequency', + answer_options: [ + { + text: localize('None'), + value: 'No transactions in the past 12 months', + }, + { + text: '1 - 5', + value: '1 - 5 transactions in the past 12 months', + }, + { + text: '6 - 10', + value: '6 - 10 transactions in the past 12 months', + }, + { + text: '11 - 39', + value: '11 - 39 transactions in the past 12 months', + }, + { + text: localize('40 or more'), + value: '40 transactions or more in the past 12 months', + }, + ], + }, + { + question_text: localize('How much experience do you have with other financial instruments?'), + field_type: 'dropdown', + form_control: 'trading_experience_financial_instruments', + answer_options: [ + { + text: localize('No experience'), + value: 'No experience', + }, + { + text: localize('Less than a year'), + value: 'Less than a year', + }, + { + text: localize('1 - 2 years'), + value: '1 - 2 years', + }, + { + text: localize('Over 3 years'), + value: 'Over 3 years', + }, + ], + }, + { + question_text: localize( + 'How many trades have you placed with other financial instruments in the past 12 months?' + ), + form_control: 'trading_frequency_financial_instruments', + field_type: 'dropdown', + answer_options: [ + { + text: localize('None'), + value: 'No transactions in the past 12 months', + }, + { + text: '1 - 5', + value: '1 - 5 transactions in the past 12 months', + }, + { + text: '6 - 10', + value: '6 - 10 transactions in the past 12 months', + }, + { + text: '11 - 39', + value: '11 - 39 transactions in the past 12 months', + }, + { + text: localize('40 or more'), + value: '40 transactions or more in the past 12 months', + }, + ], + }, + ], + }, + { + question_text: localize('In your understanding, CFD trading allows you to'), + section: 'trading_knowledge', + form_control: 'cfd_trading_definition', + field_type: 'radio', + answer_options: [ + { + text: localize('Purchase commodities or shares of a company.'), + value: 'Purchase shares of a company or physical commodities.', + }, + { + text: localize( + 'Place a bet on the price movement of an asset where the outcome is a fixed return or nothing at all.' + ), + value: 'Place a bet on the price movement.', + }, + { + text: localize('Speculate on the price movement of an asset without actually owning it.'), + value: 'Speculate on the price movement.', + }, + { + text: localize('Make a long-term investment for a guaranteed profit.'), + value: 'Make a long-term investment.', + }, + ], + }, + { + question_text: localize('How does leverage affect CFD trading?'), + section: 'trading_knowledge', + form_control: 'leverage_impact_trading', + field_type: 'radio', + answer_options: [ + { + text: localize('Leverage helps to mitigate risk.'), + value: 'Leverage is a risk mitigation technique.', + }, + { + text: localize('Leverage prevents you from opening large positions.'), + value: 'Leverage prevents you from opening large positions.', + }, + { text: localize('Leverage guarantees profits.'), value: 'Leverage guarantees profits.' }, + { + text: localize( + 'Leverage lets you open large positions for a fraction of trade value, which may result in increased profit or loss.' + ), + value: "Leverage lets you open larger positions for a fraction of the trade's value.", + }, + ], + }, + { + question_text: localize( + "Leverage trading is high-risk, so it's a good idea to use risk management features such as stop loss. Stop loss allows you to" + ), + section: 'trading_knowledge', + form_control: 'leverage_trading_high_risk_stop_loss', + field_type: 'radio', + answer_options: [ + { + text: localize('Cancel your trade at any time within a specified time frame.'), + value: 'Cancel your trade at any time within a chosen timeframe.', + }, + { + text: localize( + 'Close your trade automatically when the loss is equal to or more than a specified amount, as long as there is adequate market liquidity.' + ), + value: 'Close your trade automatically when the loss is more than or equal to a specific amount.', + }, + { + text: localize( + 'Close your trade automatically when the profit is equal to or more than a specified amount, as long as there is adequate market liquidity.' + ), + value: 'Close your trade automatically when the profit is more than or equal to a specific amount.', + }, + { + text: localize('Make a guaranteed profit on your trade.'), + value: 'Make a guaranteed profit on your trade.', + }, + ], + }, + { + question_text: localize('When do you be required to pay an initial margin?'), + section: 'trading_knowledge', + form_control: 'required_initial_margin', + field_type: 'radio', + answer_options: [ + { + text: localize('When opening a leveraged CFD trade'), + value: 'When opening a Leveraged CFD trade.', + }, + { text: localize('When trading multipliers'), value: 'When trading Multipliers.' }, + { + text: localize('When buying shares of a company'), + value: 'When buying shares of a company.', + }, + { text: localize('All of the above'), value: 'All of the above.' }, + ], + }, +]; + +const default_form_config = { + supported_in: ['maltainvest'], + default_value: '', +}; + +export const trading_assessment_form_config = { + risk_tolerance: { + ...default_form_config, + }, + source_of_experience: { + ...default_form_config, + }, + cfd_experience: { + ...default_form_config, + }, + cfd_frequency: { + ...default_form_config, + }, + trading_experience_financial_instruments: { + ...default_form_config, + }, + trading_frequency_financial_instruments: { + ...default_form_config, + }, + cfd_trading_definition: { + ...default_form_config, + }, + leverage_impact_trading: { + ...default_form_config, + }, + leverage_trading_high_risk_stop_loss: { + ...default_form_config, + }, + required_initial_margin: { + ...default_form_config, + }, +}; + +const tradingAssessmentConfig = ({ real_account_signup_target }, TradingAssessmentNewUser) => { + return { + header: { + active_title: localize('Complete your trading assessment'), + title: localize('Trading assessment'), + }, + body: TradingAssessmentNewUser, + form_value: getDefaultFields(real_account_signup_target, trading_assessment_form_config), + props: { + validate: generateValidationFunction(real_account_signup_target, trading_assessment_form_config), + assessment_questions: trading_assessment_questions, + }, + }; +}; + +export default tradingAssessmentConfig; diff --git a/packages/account/src/Constants/routes-config.js b/packages/account/src/Constants/routes-config.js index 57644c6e903c..134707a5b949 100644 --- a/packages/account/src/Constants/routes-config.js +++ b/packages/account/src/Constants/routes-config.js @@ -5,6 +5,7 @@ import { AccountLimits, Passwords, PersonalDetails, + TradingAssessment, FinancialAssessment, ProofOfIdentity, ProofOfAddress, @@ -53,6 +54,17 @@ const initRoutesConfig = ({ is_appstore }) => [ getTitle: () => localize('Personal details'), default: true, }, + ], + }, + { + getTitle: () => localize('Assessments'), + icon: 'IcAssessment', + subroutes: [ + { + path: routes.trading_assessment, + component: TradingAssessment, + getTitle: () => localize('Trading assessment'), + }, { path: routes.financial_assessment, component: FinancialAssessment, diff --git a/packages/account/src/Containers/account.jsx b/packages/account/src/Containers/account.jsx index daf957557296..04ff255f0c11 100644 --- a/packages/account/src/Containers/account.jsx +++ b/packages/account/src/Containers/account.jsx @@ -80,11 +80,13 @@ const PageOverlayWrapper = ({ }; const Account = ({ + active_account_landing_company, currency, history, is_from_derivgo, is_logged_in, is_logging_in, + is_risky_client, is_virtual, is_visible, location, @@ -113,7 +115,14 @@ const Account = ({ routes.forEach(menu_item => { menu_item.subroutes.forEach(route => { if (route.path === shared_routes.financial_assessment) { - route.is_disabled = is_virtual; + if (active_account_landing_company === 'maltainvest') { + route.is_disabled = is_virtual || !is_risky_client; + } else { + route.is_disabled = is_virtual; + } + } + if (route.path === shared_routes.trading_assessment) { + route.is_disabled = is_virtual || active_account_landing_company !== 'maltainvest'; } if (route.path === shared_routes.proof_of_identity || route.path === shared_routes.proof_of_address) { @@ -172,11 +181,13 @@ const Account = ({ }; Account.propTypes = { + active_account_landing_company: PropTypes.string, currency: PropTypes.string, history: PropTypes.object, is_logged_in: PropTypes.bool, is_logging_in: PropTypes.bool, is_from_derivgo: PropTypes.bool, + is_risky_client: PropTypes.bool, is_virtual: PropTypes.bool, is_visible: PropTypes.bool, location: PropTypes.object, @@ -189,10 +200,12 @@ Account.propTypes = { }; export default connect(({ client, common, ui }) => ({ + active_account_landing_company: client.landing_company_shortcode, currency: client.currency, is_logged_in: client.is_logged_in, is_logging_in: client.is_logging_in, is_from_derivgo: common.is_from_derivgo, + is_risky_client: client.is_risky_client, is_virtual: client.is_virtual, is_visible: ui.is_account_settings_visible, logout: client.logout, diff --git a/packages/account/src/Sections/Profile/FinancialAssessment/financial-assessment.jsx b/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx similarity index 89% rename from packages/account/src/Sections/Profile/FinancialAssessment/financial-assessment.jsx rename to packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx index 8d46801f8b11..e5a6a9cd6058 100644 --- a/packages/account/src/Sections/Profile/FinancialAssessment/financial-assessment.jsx +++ b/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx @@ -178,13 +178,16 @@ const SubmittedPage = ({ platform, routeBackInApp }) => { const FinancialAssessment = ({ is_authentication_needed, is_financial_account, + is_mf, is_svg, is_trading_experience_incomplete, + is_financial_information_incomplete, is_virtual, platform, - removeNotificationByKey, - removeNotificationMessage, + refreshNotifications, routeBackInApp, + setFinancialAndTradingAssessment, + updateAccountStatus, }) => { const history = useHistory(); const { is_appstore } = React.useContext(PlatformContext); @@ -196,6 +199,7 @@ const FinancialAssessment = ({ const [is_btn_loading, setIsBtnLoading] = React.useState(false); const [is_submit_success, setIsSubmitSuccess] = React.useState(false); const [initial_form_values, setInitialFormValues] = React.useState({}); + const { income_source, employment_status, @@ -223,43 +227,46 @@ const FinancialAssessment = ({ } else { WS.authorized.storage.getFinancialAssessment().then(data => { WS.wait('get_account_status').then(() => { - setHasTradingExperience((is_financial_account || is_trading_experience_incomplete) && !is_svg); + setHasTradingExperience( + (is_financial_account || is_trading_experience_incomplete) && !is_svg && !is_mf + ); if (data.error) { setApiInitialLoadError(data.error.message); return; } - setInitialFormValues(data.get_financial_assessment); setIsLoading(false); }); }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const onSubmit = (values, { setSubmitting, setStatus }) => { + const onSubmit = async (values, { setSubmitting, setStatus }) => { setStatus({ msg: '' }); setIsBtnLoading(true); - WS.setFinancialAssessment(values).then(data => { - if (data.error) { + const form_payload = { + financial_information: { ...values }, + }; + const data = await setFinancialAndTradingAssessment(form_payload); + if (data.error) { + setIsBtnLoading(false); + setStatus({ msg: data.error.message }); + } else { + await updateAccountStatus(); + WS.authorized.storage.getFinancialAssessment().then(res_data => { + setInitialFormValues(res_data.get_financial_assessment); + setIsSubmitSuccess(true); setIsBtnLoading(false); - setStatus({ msg: data.error.message }); - } else { - WS.authorized.storage.getFinancialAssessment().then(res_data => { - setInitialFormValues(res_data.get_financial_assessment); - setIsSubmitSuccess(true); - setIsBtnLoading(false); - if (isDesktop()) { - setTimeout(() => setIsSubmitSuccess(false), 10000); - } - - removeNotificationMessage({ key: 'risk' }); - removeNotificationByKey({ key: 'risk' }); - }); - } + if (isDesktop()) { + setTimeout(() => setIsSubmitSuccess(false), 10000); + } + }); setSubmitting(false); - }); + refreshNotifications(); + } }; const validateFields = values => { @@ -296,40 +303,53 @@ const FinancialAssessment = ({ }; const getScrollOffset = () => { - if (isMobile()) return is_appstore ? '160px' : '200px'; + if (is_appstore) { + return isMobile() ? '160px' : '200px'; + } else if (is_mf) { + return is_financial_information_incomplete && !is_submit_success ? '140px' : '80px'; + } return '80px'; }; if (is_loading) return ; if (api_initial_load_error) return ; if (is_virtual) return ; - if (isMobile() && is_authentication_needed && is_submit_success) + if (isMobile() && is_authentication_needed && !is_mf && is_submit_success) return ; + const setInitialFormData = () => { + const form_data = { + income_source, + employment_status, + employment_industry, + occupation, + source_of_wealth, + education_level, + net_income, + estimated_worth, + account_turnover, + ...(has_trading_experience && { + binary_options_trading_experience, + binary_options_trading_frequency, + cfd_trading_experience, + cfd_trading_frequency, + forex_trading_experience, + forex_trading_frequency, + other_instruments_trading_experience, + other_instruments_trading_frequency, + }), + }; + if (!is_mf) { + return form_data; + } + delete form_data.employment_status; + return form_data; + }; + return ( {is_form_visible && (
+ {is_mf && is_financial_information_incomplete && !is_submit_success && ( +
+
+
+ + {isMobile() ? ( + + + + ) : ( + + + + )} +
+
+
+ )} -
- - - - - { - setFieldTouched('employment_status', true); - handleChange(e); - }} - /> - -
+ {!is_mf && ( +
+ + + + + { + setFieldTouched('employment_status', true); + handleChange(e); + }} + /> + +
+ )}
{status && status.msg && } - {isMobile() && !is_appstore && ( + {isMobile() && !is_appstore && !is_mf && ( ({ is_authentication_needed: client.is_authentication_needed, is_financial_account: client.is_financial_account, + is_mf: client.landing_company_shortcode === 'maltainvest', is_svg: client.is_svg, + is_financial_information_incomplete: client.is_financial_information_incomplete, is_trading_experience_incomplete: client.is_trading_experience_incomplete, is_virtual: client.is_virtual, platform: common.platform, - removeNotificationByKey: notifications.removeNotificationByKey, - removeNotificationMessage: notifications.removeNotificationMessage, + refreshNotifications: notifications.refreshNotifications, routeBackInApp: common.routeBackInApp, + setFinancialAndTradingAssessment: client.setFinancialAndTradingAssessment, + updateAccountStatus: client.updateAccountStatus, }))(withRouter(FinancialAssessment)); diff --git a/packages/account/src/Sections/Profile/FinancialAssessment/financial-information-list.js b/packages/account/src/Sections/Assessment/FinancialAssessment/financial-information-list.js similarity index 100% rename from packages/account/src/Sections/Profile/FinancialAssessment/financial-information-list.js rename to packages/account/src/Sections/Assessment/FinancialAssessment/financial-information-list.js diff --git a/packages/account/src/Sections/Assessment/FinancialAssessment/index.js b/packages/account/src/Sections/Assessment/FinancialAssessment/index.js new file mode 100644 index 000000000000..f1febc5e80fe --- /dev/null +++ b/packages/account/src/Sections/Assessment/FinancialAssessment/index.js @@ -0,0 +1,3 @@ +import FinancialAssessment from './financial-assessment.jsx'; + +export default FinancialAssessment; diff --git a/packages/account/src/Sections/Assessment/TradingAssessment/index.js b/packages/account/src/Sections/Assessment/TradingAssessment/index.js new file mode 100644 index 000000000000..edafe279455a --- /dev/null +++ b/packages/account/src/Sections/Assessment/TradingAssessment/index.js @@ -0,0 +1,3 @@ +import TradingAssessment from './trading-assessment.jsx'; + +export default TradingAssessment; diff --git a/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx b/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx new file mode 100644 index 000000000000..683e286efb23 --- /dev/null +++ b/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx @@ -0,0 +1,300 @@ +import React from 'react'; +import { localize, Localize } from '@deriv/translations'; +import FormBody from 'Components/form-body'; +import FormSubHeader from 'Components/form-sub-header'; +import { RiskToleranceWarningModal, TestWarningModal } from 'Components/trading-assessment'; +import { trading_assessment_questions } from 'Configs/trading-assessment-config.js'; +import { + DesktopWrapper, + Dropdown, + MobileWrapper, + SelectNative, + Text, + FormSubmitButton, + Button, + Loading, +} from '@deriv/components'; +import FormFooter from 'Components/form-footer'; +import { isMobile, routes, WS } from '@deriv/shared'; +import { connect } from 'Stores/connect'; +import { useHistory, withRouter } from 'react-router'; +import { Formik, Form } from 'formik'; + +const populateData = form_data => { + return { + risk_tolerance: form_data.risk_tolerance, + source_of_experience: form_data.source_of_experience, + cfd_experience: form_data.cfd_experience, + cfd_frequency: form_data.cfd_frequency, + trading_experience_financial_instruments: form_data.trading_experience_financial_instruments, + trading_frequency_financial_instruments: form_data.trading_frequency_financial_instruments, + cfd_trading_definition: form_data.cfd_trading_definition, + leverage_impact_trading: form_data.leverage_impact_trading, + leverage_trading_high_risk_stop_loss: form_data.leverage_trading_high_risk_stop_loss, + required_initial_margin: form_data.required_initial_margin, + }; +}; + +const TradingAssessment = ({ + is_virtual, + setFinancialAndTradingAssessment, + // setShouldShowWarningModal, + // should_show_risk_accept_modal, +}) => { + const history = useHistory(); + const [is_loading, setIsLoading] = React.useState(true); + const [is_btn_loading, setIsBtnLoading] = React.useState(false); + const [is_submit_success, setIsSubmitSuccess] = React.useState(false); + const [initial_form_values, setInitialFormValues] = React.useState({}); + const [should_accept_risk, setShouldAcceptRisk] = React.useState(false); + const [should_show_warning_modal, setShouldShowWarningModal] = React.useState(false); + const [form_data, setFormData] = React.useState({}); + + React.useEffect(() => { + if (is_virtual) { + setIsLoading(false); + history.push(routes.personal_details); + } else { + WS.authorized.storage.getFinancialAssessment().then(data => { + // set initial form data + setInitialFormValues(() => populateData(data.get_financial_assessment)); + setIsLoading(false); + setIsSubmitSuccess(false); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleSubmit = async values => { + setFormData(values); + setIsBtnLoading(false); + setIsSubmitSuccess(true); + if (values.risk_tolerance === 'No') { + setShouldAcceptRisk(true); + } else { + setIsBtnLoading(true); + const response = await WS.authorized.storage.getFinancialAssessment(); + const { trading_score: prev_score } = response?.get_financial_assessment; + const form_payload = { + trading_experience_regulated: { + cfd_experience: values.cfd_experience, + cfd_frequency: values.cfd_frequency, + cfd_trading_definition: values.cfd_trading_definition, + leverage_impact_trading: values.leverage_impact_trading, + leverage_trading_high_risk_stop_loss: values.leverage_trading_high_risk_stop_loss, + required_initial_margin: values.required_initial_margin, + risk_tolerance: values.risk_tolerance, + source_of_experience: values.source_of_experience, + trading_experience_financial_instruments: values.trading_experience_financial_instruments, + trading_frequency_financial_instruments: values.trading_frequency_financial_instruments, + }, + }; + const data = await setFinancialAndTradingAssessment(form_payload); + const { trading_score } = data?.set_financial_assessment; + setShouldShowWarningModal(prev_score > 0 && trading_score === 0); + + WS.authorized.storage.getFinancialAssessment().then(res_data => { + setInitialFormValues(res_data.get_financial_assessment); + }); + setIsBtnLoading(false); + setTimeout(() => setIsSubmitSuccess(false), 10000); + } + }; + + const handleAcceptRisk = async () => { + await handleSubmit({ ...form_data, risk_tolerance: 'Yes' }); + setShouldAcceptRisk(false); + setIsBtnLoading(false); + setIsSubmitSuccess(false); + }; + + if (is_loading) return ; + if (should_accept_risk) { + return ( + ]} + /> + } + /> + ); + } + if (should_show_warning_modal) { + return ( + + ]} + /> + ]} + /> + + + } + footer_content={ +
)} + {'employment_status' in values && ( +
+ + + + + { + setFieldTouched('employment_status', true); + handleChange(e); + }} + /> + +
+ )}
)} diff --git a/packages/account/src/Sections/index.js b/packages/account/src/Sections/index.js index 680b801302d1..e249537b2086 100644 --- a/packages/account/src/Sections/index.js +++ b/packages/account/src/Sections/index.js @@ -1,7 +1,8 @@ import Passwords from 'Sections/Security/Passwords'; import AccountLimits from 'Sections/Security/AccountLimits'; import PersonalDetails from 'Sections/Profile/PersonalDetails'; -import FinancialAssessment from 'Sections/Profile/FinancialAssessment'; +import TradingAssessment from 'Sections/Assessment/TradingAssessment'; +import FinancialAssessment from 'Sections/Assessment/FinancialAssessment'; import ProofOfIdentity from 'Sections/Verification/ProofOfIdentity'; import ProofOfAddress from 'Sections/Verification/ProofOfAddress'; import TwoFactorAuthentication from 'Sections/Security/TwoFactorAuthentication'; @@ -18,6 +19,7 @@ export { AccountLimits, Passwords, PersonalDetails, + TradingAssessment, FinancialAssessment, ProofOfIdentity, ProofOfAddress, diff --git a/packages/account/src/Styles/account.scss b/packages/account/src/Styles/account.scss index daa1a99687ce..111f134a9667 100644 --- a/packages/account/src/Styles/account.scss +++ b/packages/account/src/Styles/account.scss @@ -48,6 +48,7 @@ $MIN_HEIGHT_FLOATING: calc( & .account-form { &__personal-details, + &__trading-assessment, &__financial-assessment { .account__scrollbars_container { width: 68.5rem; @@ -199,11 +200,20 @@ $MIN_HEIGHT_FLOATING: calc( margin-bottom: 0; } + .emp-status { + .dc-select-native__wrapper { + margin-top: 3rem; + } + } + .dc-dropdown-container { .dc-dropdown__display-text, .dc-list__item-text { text-transform: unset; } + .dc-dropdown__display { + margin-top: 3rem; + } } & .account-form__header.address { @@ -329,6 +339,30 @@ $MIN_HEIGHT_FLOATING: calc( } } } + &__question { + position: relative; + max-width: 67.2rem; + margin-top: 1.6rem; + margin-bottom: 2rem; + &--text { + margin-bottom: 1.6rem; + max-width: 67.2rem; + @include mobile { + margin-bottom: 2.4rem; + } + } + .dc-dropdown__display { + max-width: 67.2rem; + } + + &:last-child { + .dc-list { + .dc-themed-scrollbars__autohide { + top: 1px; + } + } + } + } &__fieldset { position: relative; @@ -536,6 +570,7 @@ $MIN_HEIGHT_FLOATING: calc( &__personal-details { &--dashboard { + // buto & .account-form__fieldset { margin-bottom: 3.2rem; @include mobile { @@ -587,7 +622,7 @@ $MIN_HEIGHT_FLOATING: calc( } } } - + &__trading-assessment, &__financial-assessment { .dc-select-native__placeholder { background-color: var(--general-main-1); @@ -2696,3 +2731,28 @@ $MIN_HEIGHT_FLOATING: calc( } /* stylelint-enable */ + +.financial-banner { + width: 49.5rem; + border-radius: 4px; + position: relaitve; + background-color: var(--status-warning-transparent); + + @include mobile { + width: 32.8rem; + margin: 1rem; + } + + &__frame { + width: 100%; + position: relative; + padding: 0.8rem; + } + &__container { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: center; + gap: 0.8rem; + } +} diff --git a/packages/cashier/src/pages/withdrawal/disable-withdrawal-modal.jsx b/packages/cashier/src/pages/withdrawal/disable-withdrawal-modal.jsx new file mode 100644 index 000000000000..2590125a0384 --- /dev/null +++ b/packages/cashier/src/pages/withdrawal/disable-withdrawal-modal.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Button, Modal, Text } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; + +const DisableWithdrawalModal = ({ is_risky_client, onClick }) => ( + + + + + + + +