+
{ this.props.symbol &&
} >
+
}
InfoBox={}
+ end_epoch={this.props.end_epoch}
+ granularity={this.props.granularity}
+ is_trade_page
onSymbolChange={this.props.onSymbolChange}
scroll_to_epoch={this.props.scroll_to_epoch}
scroll_to_offset={this.props.scroll_to_offset}
+ start_epoch={this.props.start_epoch}
should_show_bottom_widgets={should_show_bottom_widgets}
should_show_last_digit_stats={should_show_last_digit_stats}
symbol={this.props.symbol}
@@ -74,9 +84,13 @@ class Trade extends React.Component {
Trade.propTypes = {
chart_id : PropTypes.string,
- chart_zoom : PropTypes.number,
+ chart_type : PropTypes.string,
contract_type : PropTypes.string,
+ end_epoch : PropTypes.number,
+ granularity : PropTypes.number,
hidePositions : PropTypes.func,
+ is_chart_loading : PropTypes.bool,
+ is_chart_ready : PropTypes.bool,
is_contract_mode : PropTypes.bool,
is_digit_contract: PropTypes.bool,
is_mobile : PropTypes.bool,
@@ -90,6 +104,7 @@ Trade.propTypes = {
scroll_to_epoch : PropTypes.number,
scroll_to_offset : PropTypes.number,
showPositions : PropTypes.func,
+ start_epoch : PropTypes.number,
symbol : PropTypes.string,
};
@@ -98,8 +113,14 @@ export default connect(
is_digit_contract : modules.contract.is_digit_contract,
onCloseContract : modules.contract.onCloseContract,
chart_id : modules.smart_chart.chart_id,
+ chart_type : modules.smart_chart.chart_type,
scroll_to_epoch : modules.smart_chart.scroll_to_left_epoch,
scroll_to_offset : modules.smart_chart.scroll_to_left_epoch_offset,
+ granularity : modules.smart_chart.granularity,
+ end_epoch : modules.smart_chart.end_epoch,
+ start_epoch : modules.smart_chart.start_epoch,
+ is_chart_loading : modules.smart_chart.is_chart_loading,
+ is_chart_ready : modules.smart_chart.is_chart_ready,
is_contract_mode : modules.smart_chart.is_contract_mode,
contract_type : modules.trade.contract_type,
is_trade_enabled : modules.trade.is_trade_enabled,
diff --git a/src/javascript/app/Services/socket-general.js b/src/javascript/app/Services/socket-general.js
index de5b930b9b12..091ad04e1585 100644
--- a/src/javascript/app/Services/socket-general.js
+++ b/src/javascript/app/Services/socket-general.js
@@ -51,7 +51,7 @@ const BinarySocketGeneral = (() => {
} else {
client_store.responseAuthorize(response);
WS.subscribeBalance(ResponseHandlers.balance, true);
- WS.getSettings();
+ WS.sendRequest({ get_settings: 1 }, { forced: true });
WS.getAccountStatus();
WS.payoutCurrencies();
WS.mt5LoginList();
diff --git a/src/javascript/app/Services/trackjs.js b/src/javascript/app/Services/trackjs.js
index a7ed51ce09a6..b151e048b8d5 100644
--- a/src/javascript/app/Services/trackjs.js
+++ b/src/javascript/app/Services/trackjs.js
@@ -1,3 +1,32 @@
+
+class ResponseQueue {
+ constructor() {
+ this.list = [];
+ this.size = 3;
+ }
+
+ add (response) {
+ this.list.unshift(response);
+ }
+
+ remove () {
+ this.list.pop();
+ }
+
+ push (response) {
+ if (this.list.length >= this.size) {
+ this.remove();
+ }
+ this.add(response);
+ }
+
+ fresh () {
+ this.list = [];
+ }
+}
+
+const queue = new ResponseQueue();
+
/**
* Listen on method calls and inspect the response to see if error is thrown.
* Handling the response status is NOT this function's responsibility
@@ -6,7 +35,6 @@ export const ApiCallProxyHandler = {
get(target, prop_key, receiver) {
try {
const target_value = Reflect.get(target, prop_key, receiver);
-
if (typeof target_value === 'function') {
return function(...args) {
const result = target_value.apply(this, args);
@@ -15,11 +43,18 @@ export const ApiCallProxyHandler = {
let return_value;
result.then(response => {
if (response.error) {
- window.__response_error = response; // eslint-disable-line no-underscore-dangle
+ queue.push(response);
+ if (window.TrackJS) window.TrackJS.console.log(queue.list);
+ queue.fresh();
+ if (window.TrackJS) window.TrackJS.track(response.error.code);
}
+ queue.push(response);
return_value = response;
}).catch(error => {
- window.__response_error = error; // eslint-disable-line no-underscore-dangle
+ if (window.TrackJS) {
+ window.TrackJS.console.log(queue.list);
+ window.TrackJS.track(error.getMessage());
+ }
}).finally(() => {
resolve(return_value);
});
@@ -30,7 +65,6 @@ export const ApiCallProxyHandler = {
}
return target_value;
} catch (error) {
- window.__response_error = error; // eslint-disable-line no-underscore-dangle
throw new Error(error.getMessage());
}
},
diff --git a/src/javascript/app/Services/ws-methods.js b/src/javascript/app/Services/ws-methods.js
index 3df8523ed3c3..5caf55e7b161 100644
--- a/src/javascript/app/Services/ws-methods.js
+++ b/src/javascript/app/Services/ws-methods.js
@@ -73,8 +73,8 @@ const WS = (() => {
const sellExpired = () =>
BinarySocket.send({ sell_expired: 1 });
- const sendRequest = (request_object) =>
- Promise.resolve(!isEmptyObject(request_object) ? BinarySocket.send(request_object) : {});
+ const sendRequest = (request_object, force_request) =>
+ Promise.resolve(!isEmptyObject(request_object) ? BinarySocket.send(request_object, force_request) : {});
const statement = (limit, offset, date_boundaries) =>
BinarySocket.send({ statement: 1, description: 1, limit, offset, ...date_boundaries });
diff --git a/src/javascript/app/Stores/Helpers/client-notifications.js b/src/javascript/app/Stores/Helpers/client-notifications.js
new file mode 100644
index 000000000000..5bdc048a5754
--- /dev/null
+++ b/src/javascript/app/Stores/Helpers/client-notifications.js
@@ -0,0 +1,257 @@
+import React from 'react';
+import { formatDate } from 'Utils/Date';
+import { WS } from 'Services';
+import { getRiskAssessment,
+ isAccountOfType,
+ shouldAcceptTnc,
+ shouldCompleteTax } from '_common/base/client_base';
+import { localize } from '_common/localize';
+import {
+ LocalStore,
+ State } from '_common/storage';
+import { urlFor } from '_common/url';
+import Localize from '../../App/Components/Elements/localize.jsx';
+
+// TODO: Update links to app_2 links when components are done.
+/* eslint-disable react/jsx-no-target-blank */
+const client_notifications = {
+ currency: {
+ header : localize('Set Currency'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+ self_exclusion: (excluded_until) => ({
+ header : localize('Self-exclusion Detected'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ }),
+ authenticate: {
+ header : localize('Account Authentication'),
+ message: (
+ }}
+ />
+ ),
+ type: 'info',
+ },
+ document_review: {
+ header : localize('Documents in review'),
+ message: (
+ }}
+ />
+ ),
+ type: 'info',
+ },
+ cashier_locked: {
+ header : localize('Cashier Disabled'),
+ message: localize('Deposits and withdrawals have been disabled on your account. Please check your email for more details.'),
+ type : 'warning',
+ },
+ withdrawal_locked: {
+ header : localize('Withdrawal Disabled'),
+ message: localize('Withdrawals have been disabled on your account. Please check your email for more details.'),
+ type : 'warning',
+ },
+ mt5_withdrawal_locked: {
+ header : localize('MT5 Withdrawal Disabled'),
+ message: localize('MT5 withdrawals have been disabled on your account. Please check your email for more details.'),
+ type : 'warning',
+ },
+ document_needs_action: {
+ header : localize('Authentication Failed'),
+ message: (
+ }}
+ />
+ ),
+ type: 'warning',
+ },
+ unwelcome: {
+ header : localize('Trading and Deposits Disabled'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+ mf_retail: {
+ header : localize('Binary Options Trading Disabled'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+ financial_limit: {
+ header : localize('Remove Deposit Limits'),
+ message: (
+ }}
+ />
+ ),
+ type: 'warning',
+ },
+ risk: {
+ header : localize('Withdrawal and Trading Limits'),
+ message: (
+ }}
+ />
+ ),
+ type: 'info',
+ },
+ tax: {
+ header : localize('Complete your personal details'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+ tnc: {
+ header : localize('Terms & Conditions Updated'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+ required_fields: {
+ header : localize('Complete your personal details'),
+ message: (
+ }}
+ />
+ ),
+ type: 'danger',
+ },
+};
+
+const hasMissingRequiredField = (response, client) => {
+ if (!response.get_settings) return false;
+
+ const { landing_company_shortcode } = client;
+ const is_svg = (landing_company_shortcode === 'svg' || landing_company_shortcode === 'costarica');
+
+ let required_fields;
+ if (is_svg) {
+ required_fields = getSVGRequiredFields();
+ } else {
+ required_fields = getRequiredFields();
+ }
+
+ const get_settings = response.get_settings;
+ return required_fields.some(field => !get_settings[field]);
+
+ function getSVGRequiredFields() {
+ const necessary_withdrawal_fields = State.getResponse('landing_company.financial_company.requirements.withdrawal');
+ const necessary_signup_fields = State.getResponse('landing_company.financial_company.requirements.signup')
+ .map(field => (field === 'residence' ? 'country' : field));
+
+ return [...necessary_withdrawal_fields, ...necessary_signup_fields];
+ }
+
+ function getRequiredFields() {
+ if (!isAccountOfType('financial')) return [];
+ const { residence } = client;
+
+ const required_settings_fields = [
+ 'account_opening_reason',
+ 'address_line_1',
+ 'address_city',
+ 'phone',
+ 'tax_identification_number',
+ 'tax_residence'];
+ const address_postcode_is_required = (residence === 'gb' || landing_company_shortcode === 'iom');
+ if (address_postcode_is_required) required_settings_fields.push('address_postcode');
+
+ return [...required_settings_fields];
+ }
+};
+
+const checkAccountStatus = (response, client, addNotification, loginid) => {
+ if (!response.get_account_status) return;
+ if (loginid !== LocalStore.get('active_loginid')) return;
+
+ const { prompt_client_to_authenticate, status } = response.get_account_status;
+
+ const {
+ document_under_review,
+ cashier_locked,
+ withdrawal_locked,
+ mt5_withdrawal_locked,
+ document_needs_action,
+ unwelcome,
+ ukrts_max_turnover_limit_not_set,
+ professional,
+ } = getStatusValidations(status);
+ const is_mf_retail = client.landing_company_shortcode === 'maltainvest' && !professional;
+
+ if (document_under_review) addNotification(client_notifications.document_review);
+ if (cashier_locked) addNotification(client_notifications.cashier_locked);
+ if (withdrawal_locked) addNotification(client_notifications.withdrawal_locked);
+ if (mt5_withdrawal_locked) addNotification(client_notifications.mt5_withdrawal_locked);
+ if (document_needs_action) addNotification(client_notifications.document_needs_action);
+ if (unwelcome) addNotification(client_notifications.unwelcome);
+ if (is_mf_retail) addNotification(client_notifications.mf_retail);
+ if (ukrts_max_turnover_limit_not_set) {
+ addNotification(client_notifications.financial_limit);
+ }
+ if (getRiskAssessment()) addNotification(client_notifications.risk);
+ if (shouldCompleteTax()) addNotification(client_notifications.tax);
+
+ if (prompt_client_to_authenticate && !(document_under_review || document_needs_action)) {
+ addNotification(client_notifications.authenticate);
+ }
+
+ function getStatusValidations(status_arr) {
+ return status_arr.reduce((validations, account_status) => {
+ validations[account_status] = true;
+ return validations;
+ }, {});
+ }
+};
+
+export const handleClientNotifications = (client, addNotification, loginid) => {
+ const { currency, excluded_until } = client;
+ if (!currency) addNotification(client_notifications.currency);
+ if (excluded_until) addNotification(client_notifications.self_exclusion(excluded_until));
+ if (shouldAcceptTnc()) addNotification(client_notifications.tnc);
+
+ WS.getAccountStatus().then((response) => checkAccountStatus(response, client, addNotification, loginid));
+
+ WS.sendRequest({ get_settings: 1 }, { forced: true }).then((response) => {
+ if (loginid !== LocalStore.get('active_loginid')) return;
+
+ if (hasMissingRequiredField(response, client)) {
+ addNotification(client_notifications.required_fields);
+ }
+ });
+};
diff --git a/src/javascript/app/Stores/Modules/Contract/Helpers/logic.js b/src/javascript/app/Stores/Modules/Contract/Helpers/logic.js
index 1f63702a826c..8eb622a78b1f 100644
--- a/src/javascript/app/Stores/Modules/Contract/Helpers/logic.js
+++ b/src/javascript/app/Stores/Modules/Contract/Helpers/logic.js
@@ -1,5 +1,24 @@
-import moment from 'moment';
-import ServerTime from '_common/base/server_time';
+import moment from 'moment';
+import { isEmptyObject } from '_common/utility';
+import ServerTime from '_common/base/server_time';
+
+export const getChartConfig = (contract_info, is_digit_contract) => {
+ if (isEmptyObject(contract_info)) return null;
+ const start = contract_info.date_start;
+ const end = getEndTime(contract_info);
+ const granularity = getChartGranularity(start, end);
+ const chart_type = getChartType(start, end);
+
+ return {
+ granularity,
+ start_epoch : start,
+ end_epoch : end,
+ chart_type,
+ symbol : contract_info.underlying,
+ scroll_to_epoch : contract_info.purchase_time,
+ should_show_bottom_widgets: is_digit_contract,
+ };
+};
const hour_to_granularity_map = [
[1 , 0],
diff --git a/src/javascript/app/Stores/Modules/Contract/contract-store.js b/src/javascript/app/Stores/Modules/Contract/contract-store.js
index f4bb2971de8b..bf54132f4fe3 100644
--- a/src/javascript/app/Stores/Modules/Contract/contract-store.js
+++ b/src/javascript/app/Stores/Modules/Contract/contract-store.js
@@ -15,6 +15,7 @@ import {
getDigitInfo,
isDigitContract } from './Helpers/digits';
import {
+ getChartConfig,
getChartGranularity,
getChartType,
getDisplayStatus,
@@ -39,6 +40,10 @@ export default class ContractStore extends BaseStore {
@observable error_message = '';
@observable is_sell_requested = false;
+ // ---- Replay Contract Config ----
+ @observable replay_contract_id;
+ @observable replay_info = observable.object({});
+
// ---- Normal properties ---
forget_id;
chart_type = 'mountain';
@@ -76,13 +81,16 @@ export default class ContractStore extends BaseStore {
SmartChartStore.updateGranularity(0);
SmartChartStore.updateChartType('mountain');
}
+ // Clear chart loading status once ChartListener returns ready for completed contract
+ if (!this.is_ongoing_contract) {
+ this.waitForChartListener(SmartChartStore);
+ }
+
// setters for ongoing contracts, will only init once onMount after left_epoch is set
} else if (!this.is_left_epoch_set) {
- // For tick contracts, it is necessary to set the chartType and granularity after saving and clearing trade layout
- // TODO: Fix issue with setting start_epoch and loading ongoing contract from positions
- // if (this.is_from_positions) {
- // SmartChartStore.setContractStart(date_start);
- // }
+ if (this.is_from_positions) {
+ SmartChartStore.setContractStart(date_start);
+ }
if (contract_info.tick_count) {
SmartChartStore.updateGranularity(0);
@@ -102,6 +110,10 @@ export default class ContractStore extends BaseStore {
createChartBarrier(SmartChartStore, contract_info);
createChartMarkers(SmartChartStore, contract_info);
+
+ if (this.smart_chart.is_chart_ready) {
+ this.smart_chart.setIsChartLoading(false);
+ }
}
@action.bound
@@ -116,12 +128,35 @@ export default class ContractStore extends BaseStore {
this.is_from_positions = is_from_positions;
if (contract_id) {
- this.smart_chart.saveAndClearTradeChartLayout();
+ if (this.is_from_positions) {
+ this.smart_chart.setIsChartLoading(true);
+ }
+ this.smart_chart.saveAndClearTradeChartLayout('contract');
this.smart_chart.setContractMode(true);
WS.subscribeProposalOpenContract(this.contract_id.toString(), this.updateProposal, false);
}
}
+ @action.bound
+ onMountReplay(contract_id) {
+ if (contract_id) {
+ this.smart_chart = this.root_store.modules.smart_chart;
+ this.smart_chart.setContractMode(true);
+ this.replay_contract_id = contract_id;
+ WS.subscribeProposalOpenContract(this.replay_contract_id.toString(), this.populateConfig, false);
+ }
+ }
+
+ @action.bound
+ onUnmountReplay() {
+ this.forgetProposalOpenContract();
+ this.forget_id = null;
+ this.replay_contract_id = null;
+ this.replay_info = {};
+ this.smart_chart.setContractMode(false);
+ this.smart_chart.cleanupContractChartView();
+ }
+
@action.bound
accountSwitcherListener () {
this.smart_chart.setContractMode(false);
@@ -155,12 +190,42 @@ export default class ContractStore extends BaseStore {
this.onCloseContract();
}
+ @action.bound
+ populateConfig(response) {
+ if ('error' in response) {
+ this.has_error = true;
+ this.contract_config = {};
+ this.smart_chart.setIsChartLoading(false);
+ return;
+ }
+ if (isEmptyObject(response.proposal_open_contract)) {
+ this.has_error = true;
+ this.error_message = localize('Contract does not exist or does not belong to this client.');
+ this.contract_config = {};
+ this.smart_chart.setContractMode(false);
+ this.smart_chart.setIsChartLoading(false);
+ return;
+ }
+ if (+response.proposal_open_contract.contract_id !== +this.replay_contract_id) return;
+
+ this.forget_id = response.proposal_open_contract.id;
+ this.replay_info = response.proposal_open_contract;
+
+ createChartBarrier(this.smart_chart, this.replay_info);
+ createChartMarkers(this.smart_chart, this.replay_info);
+ this.handleDigits(this.replay_info);
+
+ this.waitForChartListener(this.smart_chart);
+
+ }
+
@action.bound
updateProposal(response) {
if ('error' in response) {
this.has_error = true;
this.error_message = response.error.message;
this.contract_info = {};
+ this.smart_chart.setIsChartLoading(false);
return;
}
if (isEmptyObject(response.proposal_open_contract)) {
@@ -169,6 +234,7 @@ export default class ContractStore extends BaseStore {
this.contract_info = {};
this.contract_id = null;
this.smart_chart.setContractMode(false);
+ this.smart_chart.setIsChartLoading(false);
return;
}
if (+response.proposal_open_contract.contract_id !== +this.contract_id) return;
@@ -182,13 +248,13 @@ export default class ContractStore extends BaseStore {
this.drawChart(this.smart_chart, this.contract_info);
- this.handleDigits();
+ this.handleDigits(this.contract_info);
}
@action.bound
- handleDigits() {
+ handleDigits(contract_info) {
if (this.is_digit_contract) {
- extendObservable(this.digits_info, getDigitInfo(this.digits_info, this.contract_info));
+ extendObservable(this.digits_info, getDigitInfo(this.digits_info, contract_info));
}
}
@@ -239,9 +305,26 @@ export default class ContractStore extends BaseStore {
WS.forget('proposal_open_contract', this.updateProposal, { id: this.forget_id });
}
+ waitForChartListener = (SmartChartStore) => {
+ // TODO: Refactor, timeout interval is required for completed contracts.
+ // There is an issue when we receive the proposal_open_contract response
+ // for a completed contract and chartListener returns false for that single instance / single response.
+ // Hence, we need to set an interval to keep checking the chartListener until it returns true
+
+ let timer;
+ if (!SmartChartStore.is_chart_ready) {
+ // console.log('waiting for listener');
+ timer = setTimeout(() => this.waitForChartListener(SmartChartStore), 500);
+ } else {
+ // console.log('cleared listener');
+ SmartChartStore.setIsChartLoading(false);
+ clearTimeout(timer);
+ }
+ };
+
@action.bound
- removeSellError() {
- delete this.sell_info.error_message;
+ removeErrorMessage() {
+ delete this.error_message;
}
@action.bound
@@ -253,6 +336,11 @@ export default class ContractStore extends BaseStore {
// ---------------------------
// TODO: currently this runs on each response, even if contract_info is deep equal previous one
+ @computed
+ get replay_config() {
+ return getChartConfig(this.replay_info, this.is_digit_contract);
+ }
+
@computed
get details_expiry() {
return getDetailsExpiry(this);
@@ -265,7 +353,7 @@ export default class ContractStore extends BaseStore {
@computed
get display_status() {
- return getDisplayStatus(this.contract_info);
+ return getDisplayStatus(this.contract_info.status ? this.contract_info : this.replay_info);
}
@computed
@@ -291,7 +379,7 @@ export default class ContractStore extends BaseStore {
@computed
get is_ended() {
- return isEnded(this.contract_info);
+ return isEnded(this.contract_info.is_expired ? this.contract_info : this.replay_info);
}
@computed
@@ -316,6 +404,6 @@ export default class ContractStore extends BaseStore {
@computed
get is_digit_contract() {
- return isDigitContract(this.contract_info.contract_type);
+ return isDigitContract(this.contract_info.contract_type || this.replay_info.contract_type);
}
}
diff --git a/src/javascript/app/Stores/Modules/Portfolio/Helpers/portfolio-notifcations.js b/src/javascript/app/Stores/Modules/Portfolio/Helpers/portfolio-notifcations.js
new file mode 100644
index 000000000000..2ada1e0682fe
--- /dev/null
+++ b/src/javascript/app/Stores/Modules/Portfolio/Helpers/portfolio-notifcations.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { localize } from '_common/localize';
+import Money from '../../../../App/Components/Elements/money.jsx';
+import Localize from '../../../../App/Components/Elements/localize.jsx';
+
+export const contractSold = (currency, sold_for) => ({
+ header : localize('Contract sold'),
+ message: (
+ }}
+ />
+ ),
+ type : 'contract_sold',
+ size : 'small',
+ should_hide_close_btn: true,
+ is_auto_close : true,
+});
diff --git a/src/javascript/app/Stores/Modules/Portfolio/portfolio-store.js b/src/javascript/app/Stores/Modules/Portfolio/portfolio-store.js
index 29b7de6edcf7..13ebbe7a9686 100644
--- a/src/javascript/app/Stores/Modules/Portfolio/portfolio-store.js
+++ b/src/javascript/app/Stores/Modules/Portfolio/portfolio-store.js
@@ -5,6 +5,7 @@ import {
import { createTransformer } from 'mobx-utils';
import { WS } from 'Services';
import { formatPortfolioPosition } from './Helpers/format-response';
+import { contractSold } from './Helpers/portfolio-notifcations';
import {
getCurrentTick,
getDurationPeriod,
@@ -130,7 +131,6 @@ export default class PortfolioStore extends BaseStore {
@action.bound
handleSell(response) {
- // Toast messages are temporary UI for prompting user of sold contracts
if (response.error) {
// If unable to sell due to error, give error via pop up if not in contract mode
const i = this.getPositionIndexById(response.echo_req.sell);
@@ -148,10 +148,7 @@ export default class PortfolioStore extends BaseStore {
sell_price : response.sell.sold_for,
transaction_id: response.sell.transaction_id,
};
- this.root_store.ui.addToastMessage({
- message: `Contract was sold for ${response.sell.sold_for}.`,
- type : 'info',
- });
+ this.root_store.ui.addNotification(contractSold(this.root_store.client.currency, response.sell.sold_for));
}
}
diff --git a/src/javascript/app/Stores/Modules/SmartChart/Constants/markers.js b/src/javascript/app/Stores/Modules/SmartChart/Constants/markers.js
index 9cc54bbaf766..c9ecaa9295c2 100644
--- a/src/javascript/app/Stores/Modules/SmartChart/Constants/markers.js
+++ b/src/javascript/app/Stores/Modules/SmartChart/Constants/markers.js
@@ -61,7 +61,7 @@ export const MARKER_TYPES_CONFIG = {
SPOT_SELL: {
type : 'SPOT_SELL',
marker_config : MARKER_CONTENT_TYPES.SPOT_SELL,
- content_config: { className: 'chart-spot__spot' },
+ content_config: { className: 'chart-spot__sell' },
},
SPOT_EXIT: {
type : 'SPOT_EXIT',
diff --git a/src/javascript/app/Stores/Modules/SmartChart/smart-chart-store.js b/src/javascript/app/Stores/Modules/SmartChart/smart-chart-store.js
index 26ab1b13301c..8281c3d587f3 100644
--- a/src/javascript/app/Stores/Modules/SmartChart/smart-chart-store.js
+++ b/src/javascript/app/Stores/Modules/SmartChart/smart-chart-store.js
@@ -30,12 +30,20 @@ export default class SmartChartStore extends BaseStore {
@observable scroll_to_left_epoch_offset = 0;
@observable chart_id = 'trade';
+ @observable replay_id = 'contract-replay';
+ @observable is_chart_loading = false;
+ @observable is_chart_ready = false;
@observable should_import_layout = false;
@observable should_export_layout = false;
@observable should_clear_chart = false;
@observable trade_chart_layout = null;
trade_chart_symbol = null;
+ @action.bound
+ getChartStatus(status) {
+ this.is_chart_ready = status;
+ }
+
@action.bound
updateChartType(type) {
this.chart_type = type;
@@ -81,6 +89,7 @@ export default class SmartChartStore extends BaseStore {
@action.bound
onMount = () => {
+ // remove any barriers and markers before chart is ready
if (this.trade_chart_layout && !isEmptyObject(this.trade_chart_layout)) {
this.applySavedTradeChartLayout();
}
@@ -89,8 +98,6 @@ export default class SmartChartStore extends BaseStore {
@action.bound
onUnmount = () => {
this.symbol = null;
- this.removeBarriers();
- this.removeMarkers();
};
// --------- Set Contract Scroll to Left ---------
@@ -111,6 +118,11 @@ export default class SmartChartStore extends BaseStore {
this.end_epoch = end;
}
+ @action.bound
+ setIsChartLoading(bool) {
+ this.is_chart_loading = bool;
+ }
+
// ---------- Barriers ----------
@action.bound
createBarriers = (contract_type, high_barrier, low_barrier, onChartBarrierChange, barrier_config) => {
@@ -142,7 +154,9 @@ export default class SmartChartStore extends BaseStore {
@action.bound
updateBarrierColor(is_dark_mode) {
- this.barriers.main.updateBarrierColor(is_dark_mode);
+ if (!isEmptyObject(this.barriers.main)) {
+ this.barriers.main.updateBarrierColor(is_dark_mode);
+ }
}
@action.bound
@@ -151,15 +165,18 @@ export default class SmartChartStore extends BaseStore {
}
@action.bound
- saveAndClearTradeChartLayout() {
+ saveAndClearTradeChartLayout(chart_id) {
this.should_export_layout = true;
this.should_import_layout = false;
this.trade_chart_symbol = this.root_store.modules.trade.symbol;
- this.chart_id = 'contract';
+ this.chart_id = chart_id;
}
@action.bound
applySavedTradeChartLayout() {
+ if (!this.trade_chart_layout) return;
+
+ this.setIsChartLoading(true);
this.should_export_layout = false;
this.should_import_layout = true;
this.should_clear_chart = false;
@@ -173,6 +190,11 @@ export default class SmartChartStore extends BaseStore {
if (this.trade_chart_symbol !== this.root_store.modules.trade.symbol) {
this.root_store.modules.trade.updateSymbol(this.trade_chart_symbol);
}
+
+ // Clear chart loading status once ChartListener returns ready
+ if (this.is_chart_ready) {
+ this.setIsChartLoading(false);
+ }
});
}
@@ -212,7 +234,7 @@ export default class SmartChartStore extends BaseStore {
// ---------- Chart Settings ----------
@computed
- get settings() { // TODO: consider moving chart settings from ui_store to chart_store
+ get settings() {
return (({ common, ui } = this.root_store) => ({
assetInformation: ui.is_chart_asset_info_visible,
countdown : ui.is_chart_countdown_visible,
diff --git a/src/javascript/app/Stores/Modules/Trading/trade-store.js b/src/javascript/app/Stores/Modules/Trading/trade-store.js
index 95185ce930a2..4524b934cc2d 100644
--- a/src/javascript/app/Stores/Modules/Trading/trade-store.js
+++ b/src/javascript/app/Stores/Modules/Trading/trade-store.js
@@ -103,9 +103,6 @@ export default class TradeStore extends BaseStore {
@observable proposal_info = {};
@observable purchase_info = {};
- // Loading
- @observable loading_status = '';
-
// Query string
query = '';
@@ -174,9 +171,11 @@ export default class TradeStore extends BaseStore {
const active_symbols = await WS.activeSymbols();
if (active_symbols.error) {
this.root_store.common.showError(localize('Trading is unavailable at this time.'));
+ this.root_store.ui.setAppLoading(false);
return;
} else if (!active_symbols.active_symbols || !active_symbols.active_symbols.length) {
showUnavailableLocationError(this.root_store.common.showError);
+ this.root_store.ui.setAppLoading(false);
return;
}
@@ -186,7 +185,7 @@ export default class TradeStore extends BaseStore {
// Changes the symbol in query string to default symbol since the account doesn't have access to the defined symbol.
if (is_invalid_symbol) {
- this.root_store.ui.addToastMessage({
+ this.root_store.ui.addNotification({
message: localize('Certain trade parameters have been changed due to your account settings.'),
type : 'info',
});
@@ -372,7 +371,7 @@ export default class TradeStore extends BaseStore {
proposal_info : {},
});
- if (!this.smart_chart.is_contract_mode) {
+ if (!this.root_store.modules.smart_chart.is_contract_mode) {
const is_barrier_changed = 'barrier_1' in new_state || 'barrier_2' in new_state;
if (is_barrier_changed) {
this.smart_chart.updateBarriers(this.barrier_1, this.barrier_2);
@@ -446,7 +445,7 @@ export default class TradeStore extends BaseStore {
[contract_type]: getProposalInfo(this, response, obj_prev_contract_basis),
};
- if (!this.smart_chart.is_contract_mode) {
+ if (!this.root_store.modules.smart_chart.is_contract_mode) {
const color = this.root_store.ui.is_dark_mode_on ? BARRIER_COLORS.DARK_GRAY : BARRIER_COLORS.GRAY;
const barrier_config = { color };
setChartBarrier(this.smart_chart, response, this.onChartBarrierChange, barrier_config);
@@ -474,11 +473,6 @@ export default class TradeStore extends BaseStore {
this.processNewValuesAsync({ contract_type: parseInt(this.is_equal) ? 'rise_fall_equal' : 'rise_fall' }, true);
}
- @action.bound
- updateLoadingStatus(status) {
- this.loading_status = status;
- }
-
@action.bound
updateQueryString() {
// Update the url's query string by default values of the store
@@ -550,28 +544,17 @@ export default class TradeStore extends BaseStore {
this.debouncedProposal();
runInAction(() => {
this.is_trade_component_mounted = true;
+ this.onLoadingMount();
});
this.updateQueryString();
this.onSwitchAccount(this.accountSwitcherListener);
- this.onLoadingMount();
}
@action.bound
onLoadingMount() {
- setTimeout(() => {
- this.updateLoadingStatus(localize('Retrieving market symbols...'));
- });
- setTimeout(() => {
- this.updateLoadingStatus('');
- this.updateLoadingStatus(localize('Retrieving trading times...'));
- }, 1000);
- setTimeout(() => {
- this.updateLoadingStatus('');
- this.updateLoadingStatus(localize('Retrieving chart data...'));
- }, 2000);
- setTimeout(() => {
+ BinarySocket.wait('history').then(() => {
this.root_store.ui.setAppLoading(false);
- }, 3250);
+ });
}
@action.bound
diff --git a/src/javascript/app/Stores/client-store.js b/src/javascript/app/Stores/client-store.js
index ffb322e6f629..edf431885ffe 100644
--- a/src/javascript/app/Stores/client-store.js
+++ b/src/javascript/app/Stores/client-store.js
@@ -2,21 +2,22 @@ import {
action,
computed,
observable,
- when } from 'mobx';
-import moment from 'moment';
+ when } from 'mobx';
+import moment from 'moment';
import {
requestLogout,
- WS } from 'Services';
-import { getAccountTitle } from '_common/base/client_base';
-import GTM from '_common/base/gtm';
-import BinarySocket from '_common/base/socket_base';
-import * as SocketCache from '_common/base/socket_cache';
-import { localize } from '_common/localize';
+ WS } from 'Services';
+import { getAccountTitle } from '_common/base/client_base';
+import GTM from '_common/base/gtm';
+import BinarySocket from '_common/base/socket_base';
+import * as SocketCache from '_common/base/socket_cache';
+import { localize } from '_common/localize';
import {
LocalStore,
- State } from '_common/storage';
-import BaseStore from './base-store';
-import { buildCurrenciesList } from './Modules/Trading/Helpers/currency';
+ State } from '_common/storage';
+import BaseStore from './base-store';
+import { buildCurrenciesList } from './Modules/Trading/Helpers/currency';
+import { handleClientNotifications } from './Helpers/client-notifications';
const storage_key = 'client.accounts';
export default class ClientStore extends BaseStore {
@@ -226,6 +227,7 @@ export default class ClientStore extends BaseStore {
*/
@action.bound
switchAccount(loginid) {
+ this.root_store.ui.removeAllNotifications();
this.switched = loginid;
}
@@ -246,6 +248,13 @@ export default class ClientStore extends BaseStore {
this.accounts = LocalStore.getObject(storage_key);
this.switched = '';
+ const client = this.accounts[this.loginid];
+ if (client && !client.is_virtual) {
+ BinarySocket.wait('landing_company', 'website_status').then(() => {
+ handleClientNotifications(client, this.root_store.ui.addNotification, this.loginid);
+ });
+ }
+
this.selectCurrency('');
this.responsePayoutCurrencies(await WS.payoutCurrencies());
@@ -313,9 +322,9 @@ export default class ClientStore extends BaseStore {
if (!this.switched || !this.switched.length || !this.getAccount(this.switched).token) {
// Logout if the switched_account doesn't belong to any loginid.
if (!this.all_loginids.some(id => id !== this.switched) || this.switched === this.loginid) {
- this.root_store.ui.addToastMessage({
+ this.root_store.ui.addNotification({
message: localize('Could not switch to default account.'),
- type : 'error',
+ type : 'danger',
});
// request a logout
requestLogout();
@@ -323,7 +332,7 @@ export default class ClientStore extends BaseStore {
}
// Send a toast message to let the user know we can't switch his account.
- this.root_store.ui.addToastMessage({
+ this.root_store.ui.addNotification({
message: localize('Switching to default account.'),
type : 'info',
});
@@ -379,6 +388,7 @@ export default class ClientStore extends BaseStore {
this.accounts = [];
this.currencies_list = {};
this.selected_currency = '';
+ this.root_store.ui.removeAllNotifications();
}
}
diff --git a/src/javascript/app/Stores/ui-store.js b/src/javascript/app/Stores/ui-store.js
index 381ed00340bd..fd7575b4ac43 100644
--- a/src/javascript/app/Stores/ui-store.js
+++ b/src/javascript/app/Stores/ui-store.js
@@ -2,12 +2,13 @@ import {
action,
autorun,
computed,
- observable } from 'mobx';
+ observable } from 'mobx';
import {
MAX_MOBILE_WIDTH,
- MAX_TABLET_WIDTH } from 'Constants/ui';
-import { unique } from '_common/utility';
-import BaseStore from './base-store';
+ MAX_TABLET_WIDTH } from 'Constants/ui';
+import { unique } from '_common/utility';
+import BaseStore from './base-store';
+import { sortNotifications } from '../App/Components/Elements/NotificationMessage';
const store_name = 'ui_store';
@@ -39,8 +40,8 @@ export default class UIStore extends BaseStore {
@observable screen_width = window.innerWidth;
+ @observable notification_messages = [];
@observable push_notifications = [];
- @observable toast_messages = [];
@observable is_advanced_duration = false;
@observable advanced_duration_unit = 't';
@@ -52,8 +53,9 @@ export default class UIStore extends BaseStore {
@observable duration_h = 1;
@observable duration_d = 1;
- @observable is_fully_blurred = false;
- @observable is_app_blurred = false;
+ @observable is_fully_blurred = false;
+ @observable is_app_blurred = false;
+ @observable is_route_blurred = false;
@observable show_positions_toggle = true;
getDurationFromUnit = (unit) => this[`duration_${unit}`];
@@ -117,6 +119,16 @@ export default class UIStore extends BaseStore {
return this.screen_width <= MAX_TABLET_WIDTH;
}
+ @action.bound
+ showRouteBlur() {
+ this.is_route_blurred = true;
+ }
+
+ @action.bound
+ hideRouteBlur() {
+ this.is_route_blurred = false;
+ }
+
@action.bound
showAppBlur() {
this.is_app_blurred = true;
@@ -246,21 +258,21 @@ export default class UIStore extends BaseStore {
}
@action.bound
- addToastMessage(toast_message) {
- this.toast_messages.push(toast_message);
+ addNotification(notification) {
+ this.notification_messages = [...this.notification_messages, notification].sort(sortNotifications);
}
@action.bound
- removeToastMessage(toast_message) {
- const index = this.toast_messages.indexOf(toast_message);
+ removeNotification(notification) {
+ const index = this.notification_messages.indexOf(notification);
if (index > -1) {
- this.toast_messages.splice(index, 1);
+ this.notification_messages.splice(index, 1);
}
}
@action.bound
- removeAllToastMessages() {
- this.toast_messages = [];
+ removeAllNotifications() {
+ this.notification_messages = [];
}
@action.bound
diff --git a/src/root_files/_common/404.html b/src/root_files/_common/404.html
index ee4c38674b96..8b29b91548a6 100644
--- a/src/root_files/_common/404.html
+++ b/src/root_files/_common/404.html
@@ -1,7 +1,7 @@
-
DTrader
+ Deriv
- DTrader
+ Deriv