diff --git a/packages/components/src/components/input-field/input-field.jsx b/packages/components/src/components/input-field/input-field.jsx index e0d32dee7e6c..182e028b9b1b 100644 --- a/packages/components/src/components/input-field/input-field.jsx +++ b/packages/components/src/components/input-field/input-field.jsx @@ -11,9 +11,11 @@ const InputField = ({ ariaLabel, checked, className, + classNameDynamicSuffix, classNameInlinePrefix, classNameInput, classNamePrefix, + classNameWrapper, currency, current_focus, data_tip, @@ -45,6 +47,7 @@ const InputField = ({ min_value, name, format, + onBlur, onChange, onClick, onClickInputWrapper, @@ -184,10 +187,13 @@ const InputField = ({ }; const updateValue = (new_value, is_long_press) => { - const formatted_value = format ? format(new_value) : new_value; + let formatted_value = format ? format(new_value) : new_value; if (is_long_press) { setLocalValue(formatted_value); } else { + if (is_signed && /^\d+/.test(formatted_value) && formatted_value > 0) { + formatted_value = `+${formatted_value}`; + } onChange({ target: { value: formatted_value, name } }); } }; @@ -226,6 +232,7 @@ const InputField = ({ { 'input--error': has_error }, classNameInput )} + classNameDynamicSuffix={classNameDynamicSuffix} classNameInlinePrefix={classNameInlinePrefix} data_tip={data_tip} data_value={data_value} @@ -241,6 +248,7 @@ const InputField = ({ is_read_only={is_read_only} max_length={max_length} name={name} + onBlur={onBlur} onClick={onClick} onKeyPressed={onKeyPressed} placeholder={placeholder} @@ -285,9 +293,13 @@ const InputField = ({ )} {is_increment_input ? (
{increment_buttons} {input} @@ -324,7 +336,9 @@ InputField.propTypes = { className: PropTypes.string, classNameInlinePrefix: PropTypes.string, classNameInput: PropTypes.string, + classNameDynamicSuffix: PropTypes.string, classNamePrefix: PropTypes.string, + classNameWrapper: PropTypes.string, // CSS class for the component wrapper currency: PropTypes.string, current_focus: PropTypes.string, decimal_point_change: PropTypes.number, // Specify which decimal point must be updated when the increment/decrement button is pressed @@ -349,6 +363,7 @@ InputField.propTypes = { label: PropTypes.string, max_length: PropTypes.number, name: PropTypes.string, + onBlur: PropTypes.func, onChange: PropTypes.func, onClick: PropTypes.func, onClickInputWrapper: PropTypes.func, diff --git a/packages/components/src/components/input-field/input.jsx b/packages/components/src/components/input-field/input.jsx index e54f8624d3df..c104c3f8644b 100644 --- a/packages/components/src/components/input-field/input.jsx +++ b/packages/components/src/components/input-field/input.jsx @@ -9,6 +9,7 @@ const Input = ({ checked, className, classNameInlinePrefix, + classNameDynamicSuffix, current_focus, data_value, data_tip, @@ -24,6 +25,7 @@ const Input = ({ is_read_only, max_length, name, + onBlur, onClick, onKeyPressed, placeholder, @@ -38,7 +40,12 @@ const Input = ({ } }, [current_focus, name]); - const onBlur = () => setCurrentFocus(null); + const onBlurHandler = e => { + setCurrentFocus(null); + if (onBlur) { + onBlur(e); + } + }; const onFocus = () => setCurrentFocus(name); const onChange = e => { @@ -59,7 +66,7 @@ const Input = ({ }; return ( - +
{!!inline_prefix && (
- +
); }; @@ -107,6 +115,7 @@ Input.propTypes = { checked: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), className: PropTypes.string, classNameInlinePrefix: PropTypes.string, + classNameDynamicSuffix: PropTypes.string, current_focus: PropTypes.string, data_tip: PropTypes.string, data_value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), @@ -121,6 +130,7 @@ Input.propTypes = { is_read_only: PropTypes.bool, max_length: PropTypes.number, name: PropTypes.string, + onBlur: PropTypes.func, onClick: PropTypes.func, onKeyPressed: PropTypes.func, placeholder: PropTypes.string, diff --git a/packages/p2p/src/components/app.jsx b/packages/p2p/src/components/app.jsx index 17079db08fd0..015d194b27b1 100644 --- a/packages/p2p/src/components/app.jsx +++ b/packages/p2p/src/components/app.jsx @@ -11,7 +11,7 @@ import { setLanguage } from './i18next'; import './app.scss'; const App = props => { - const { general_store, order_store } = useStores(); + const { floating_rate_store, general_store, order_store } = useStores(); const { className, history, lang, order_id, server_time, websocket_api } = props; React.useEffect(() => { @@ -43,6 +43,8 @@ const App = props => { } }); general_store.setP2PConfig(); + const { currency, local_currency_config } = general_store.client; + floating_rate_store.setExchangeRate(currency, local_currency_config.currency); return () => general_store.onUnmount(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/packages/p2p/src/components/buy-sell/buy-sell-row.jsx b/packages/p2p/src/components/buy-sell/buy-sell-row.jsx index b3d273b20c7f..33be559c82d3 100644 --- a/packages/p2p/src/components/buy-sell/buy-sell-row.jsx +++ b/packages/p2p/src/components/buy-sell/buy-sell-row.jsx @@ -2,17 +2,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Table, Text, Button, Icon } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; +import { formatMoney, isMobile } from '@deriv/shared'; import { observer } from 'mobx-react-lite'; import { buy_sell } from 'Constants/buy-sell'; import { Localize, localize } from 'Components/i18next'; import UserAvatar from 'Components/user/user-avatar'; +import { ad_type } from 'Constants/floating-rate'; import { useStores } from 'Stores'; import './buy-sell-row.scss'; import TradeBadge from '../trade-badge'; const BuySellRow = ({ row: advert }) => { - const { buy_sell_store, general_store } = useStores(); + const { buy_sell_store, floating_rate_store, general_store } = useStores(); if (advert.id === 'WATCH_THIS_SPACE') { // This allows for the sliding animation on the Buy/Sell toggle as it pushes @@ -41,11 +42,15 @@ const BuySellRow = ({ row: advert }) => { min_order_amount_limit_display, payment_method_names, price_display, + rate_type, + rate, } = advert; const is_my_advert = advert.advertiser_details.id === general_store.advertiser_id; const is_buy_advert = counterparty_type === buy_sell.BUY; const { name: advertiser_name } = advert.advertiser_details; + const display_effective_rate = + rate_type === ad_type.FIXED ? price_display : parseFloat(floating_rate_store.exchange_rate * (1 + rate / 100)); if (isMobile()) { return ( @@ -88,7 +93,7 @@ const BuySellRow = ({ row: advert }) => { /> - {price_display} {local_currency} + {formatMoney(local_currency, display_effective_rate, true)} {local_currency} { - {price_display} {local_currency} + {formatMoney(local_currency, display_effective_rate, true)} {local_currency} diff --git a/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js new file mode 100644 index 000000000000..7d96d0936c06 --- /dev/null +++ b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import FloatingRate from '../floating-rate.jsx'; + +jest.mock('Stores', () => ({ + ...jest.requireActual('Stores'), + useStores: jest.fn().mockReturnValue({ + general_store: { + current_focus: '', + client: { local_currency_config: { decimal_places: 2 } }, + setCurrentFocus: jest.fn(), + }, + }), +})); + +describe('', () => { + it('should render default state of the component with hint message and increment, decrement buttons', () => { + render(); + + expect(screen.getByText('of the market rate')).toBeInTheDocument(); + expect(screen.getAllByRole('button').length).toBe(2); + }); + + it('should display error messages when error is passed as props', () => { + render(); + + expect(screen.getByText('Floating rate error')).toBeInTheDocument(); + }); + + it('should render market rate feed based on the floating rate value passed', () => { + render(); + + expect(screen.getByText('Your rate is = 102.00')).toBeInTheDocument(); + }); + + it('should render the exchange rate in hint', () => { + render(); + + expect(screen.getByText('1 AED = 20.00 INR')).toBeInTheDocument(); + }); +}); diff --git a/packages/p2p/src/components/floating-rate/floating-rate.jsx b/packages/p2p/src/components/floating-rate/floating-rate.jsx index 2a80157a7e01..c0fcaa47069a 100644 --- a/packages/p2p/src/components/floating-rate/floating-rate.jsx +++ b/packages/p2p/src/components/floating-rate/floating-rate.jsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite'; import PropTypes from 'prop-types'; import React from 'react'; import { InputField, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; +import { formatMoney, isMobile } from '@deriv/shared'; import { localize } from 'Components/i18next'; import { useStores } from 'Stores'; import './floating-rate.scss'; @@ -15,23 +15,43 @@ const FloatingRate = ({ error_messages, fiat_currency, local_currency, + onChange, offset, - placeholder, ...props }) => { const { general_store } = useStores(); const { name, value, required } = props; - const market_feed = value ? parseFloat(exchange_rate * (1 + value / 100)).toFixed(2) : exchange_rate; + const market_feed = value ? parseFloat(exchange_rate * (1 + value / 100)) : exchange_rate; + + // Input mask for formatting value on blur of floating rate field + const onBlurHandler = e => { + let float_rate = e.target.value; + if (!isNaN(float_rate) && float_rate.trim().length) { + float_rate = parseFloat(float_rate).toFixed(2); + if (/^\d+/.test(float_rate) && float_rate > 0) { + // Assign + symbol for positive rate + e.target.value = `+${float_rate}`; + } else { + e.target.value = float_rate; + } + } + onChange(e); + }; return (
+ + {localize('at')} + {localize('of the market rate')} @@ -69,9 +87,9 @@ const FloatingRate = ({ color='prominent' weight='normal' line_height='xs' - className='floating-rate__mkt-rate__msg' + className='floating-rate__mkt-rate--msg' > - {localize('1')} {fiat_currency} = {exchange_rate} {local_currency} + 1 {fiat_currency} = {formatMoney(local_currency, exchange_rate, true)} {local_currency}
@@ -81,8 +99,8 @@ const FloatingRate = ({ size='xxs' color='loss-danger' weight='normal' - line_height='s' - className='floating-rate__error_message' + line_height='xs' + className='floating-rate__error-message' > {error_messages}
@@ -95,7 +113,7 @@ const FloatingRate = ({ line_height='xs' className='floating-rate__hint' > - {localize('Your rate is')} = {market_feed} {local_currency} + {localize('Your rate is')} = {formatMoney(local_currency, market_feed, true)} {local_currency} )}
@@ -109,8 +127,8 @@ FloatingRate.propTypes = { error_messages: PropTypes.string, fiat_currency: PropTypes.string, local_currency: PropTypes.string, + onChange: PropTypes.func, offset: PropTypes.object, - placeholder: PropTypes.string, }; export default observer(FloatingRate); diff --git a/packages/p2p/src/components/floating-rate/floating-rate.scss b/packages/p2p/src/components/floating-rate/floating-rate.scss index 8d962ffbd921..9d118fa5aab6 100644 --- a/packages/p2p/src/components/floating-rate/floating-rate.scss +++ b/packages/p2p/src/components/floating-rate/floating-rate.scss @@ -3,46 +3,50 @@ flex-direction: column; &__field { display: flex; + align-items: center; + + @include mobile { + margin-top: -3rem; + } + + &--prefix { + margin-right: 2rem; + } } &__input { - height: 40px; + height: 4rem; align-self: center; appearance: none; box-sizing: border-box; border-radius: $BORDER_RADIUS; color: var(--text-general); - border: 1px solid var(--border-normal); background-image: none; - &:hover { - border-color: var(--border-hover); - } - &:active, - &:focus { - border-color: var(--border-active); + text-overflow: ellipsis; + @include mobile { + padding: 0; } - &--error { + &--error-field { color: $color-red-1; border-color: $color-red; } } &__percent { - position: absolute; - height: 3.2rem; - right: 4rem; - align-items: center; - justify-content: center; - display: flex; + order: 3; background: transparent; border-color: transparent; - z-index: 2; + // z-index: 2; + padding: 0 0.2rem; color: inherit; + @include mobile { + position: relative; + right: 9rem; + } &--symbol { font-size: 1.4rem; line-height: 1.5; - padding-top: 0.7rem; } &:before { @include typeface(--paragraph-center-normal-black); @@ -59,7 +63,7 @@ position: static; left: 10rem; top: 0px; - background: rgba(55, 124, 252, 0.08); + background-color: var(--transparent-hint); border-radius: 0px 4px 4px 0px; /* Inside auto layout */ flex: none; @@ -68,17 +72,22 @@ flex-grow: 1; gap: 0.2rem; - &-label { + @include mobile { + flex-direction: row; + align-items: center; + gap: 1rem; + } + + &--label { position: static; font-style: normal; - color: $color-black-1; flex: none; order: 1; flex-grow: 0; margin: 0px; } - &-msg { + &--msg { position: static; height: 18px; left: 8px; @@ -92,27 +101,123 @@ } &__hint { - padding-left: 1rem; + padding-left: 4rem; text-transform: none; margin-top: 0.3rem; + @include mobile { + padding-left: 1rem; + } } &__error-message { - padding-left: 1rem; + padding-left: 4rem; text-transform: none; + @include mobile { + padding-left: 1rem; + } + } + + .dc-input-wrapper { + display: flex; + align-items: center; + border: 1px solid var(--border-normal); + border-radius: 4px; + + &--error { + border: 1px solid $color-red; + } + + .dc-input-wrapper__button { + top: unset; + &--increment { + right: unset; + order: 4; + } + + &--decrement { + left: unset; + } + } + + .input { + text-align: right; + border: unset; + background-color: unset; + padding: unset; + &:focus { + border-color: unset; + } + &:hover { + border-color: unset; + } + &--has-inline-prefix { + padding-right: unset !important; + } + @include mobile { + text-align: center; + } + } + + &:hover { + border-color: var(--border-hover); + } + &:active, + &:focus { + border-color: var(--border-active); + } + + button.dc-input-wrapper__button { + position: inherit !important; + @include mobile { + // In some browsers the background color for button remains set as they consider hover as focus + &:hover { + background-color: unset !important; + } + + &:active { + background-color: var(--state-hover) !important; + } + } + } + + &--error:hover { + border-color: $color-red !important; + } + } + + .dc-input-suffix { + display: flex; + align-items: center; + + @include mobile { + width: -webkit-fill-available; + } } } .dc-input-wrapper__button, button.dc-input-wrapper__button { top: 0.6rem; + z-index: auto; } .dc-input-field { - margin: unset; + @include mobile { + width: 100%; + } + + @include desktop { + margin: unset; + } } .mobile-layout { display: flex; flex-direction: column; } + +.p2p-my-ads__form-field { + @include mobile { + height: auto !important; + } +} diff --git a/packages/p2p/src/components/my-ads/ad-status.scss b/packages/p2p/src/components/my-ads/ad-status.scss index 04cdd5420021..8282266f16c3 100644 --- a/packages/p2p/src/components/my-ads/ad-status.scss +++ b/packages/p2p/src/components/my-ads/ad-status.scss @@ -1,17 +1,41 @@ .ad-status { &--active { - background: rgba(75, 180, 179, 0.16); - border-radius: 1.6rem; - padding: 0.2rem 1.6rem; + &:before { + content: ''; + height: 100%; + width: 100%; + background-color: var(--status-success); + opacity: 0.16; + display: block; + position: absolute; + left: 0; + top: 0; + border-radius: 1.6rem; + } + padding: 0.1rem 1.2rem; text-align: center; - width: 7.4rem; + display: flex; + position: relative; + width: 6.7rem; } &--inactive { - border: 1px solid var(--status-danger); - border-radius: 0.2rem; + &:before { + content: ''; + height: 100%; + width: 100%; + background-color: var(--status-danger); + opacity: 0.16; + display: block; + position: absolute; + left: 0; + top: 0; + border-radius: 1.6rem; + } padding: 0.1rem 1.2rem; text-align: center; - width: 8.8rem; + display: flex; + position: relative; + width: 8rem; } } diff --git a/packages/p2p/src/components/my-ads/ad-type.jsx b/packages/p2p/src/components/my-ads/ad-type.jsx new file mode 100644 index 000000000000..64ad1a6c9a06 --- /dev/null +++ b/packages/p2p/src/components/my-ads/ad-type.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { localize } from 'Components/i18next'; +import { Text } from '@deriv/components'; +import './ad-type.scss'; + +const AdType = ({ float_rate }) => { + return ( +
+ + {localize('Float')} + + + {float_rate}% + +
+ ); +}; + +AdType.propTypes = { + float_rate: PropTypes.string, +}; + +export default AdType; diff --git a/packages/p2p/src/components/my-ads/ad-type.scss b/packages/p2p/src/components/my-ads/ad-type.scss new file mode 100644 index 000000000000..f12ea13ea9c6 --- /dev/null +++ b/packages/p2p/src/components/my-ads/ad-type.scss @@ -0,0 +1,16 @@ +.ad-type { + display: flex; + align-items: center; + justify-content: space-around; + + &__badge { + align-items: center; + border-radius: 0.4rem; + border: 1px solid var(--border-normal); + display: flex; + flex-direction: row; + margin: 0.25rem; + padding: 0.1rem 0.8rem; + width: fit-content; + } +} diff --git a/packages/p2p/src/components/my-ads/create-ad-form.jsx b/packages/p2p/src/components/my-ads/create-ad-form.jsx index be6d585389d8..1aeff5aff79b 100644 --- a/packages/p2p/src/components/my-ads/create-ad-form.jsx +++ b/packages/p2p/src/components/my-ads/create-ad-form.jsx @@ -14,9 +14,11 @@ import { import { formatMoney, isDesktop, isMobile, mobileOSDetect } from '@deriv/shared'; import { reaction } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { Localize, localize } from 'Components/i18next'; +import FloatingRate from 'Components/floating-rate'; import { useUpdatingAvailableBalance } from 'Components/hooks'; +import { Localize, localize } from 'Components/i18next'; import { buy_sell } from 'Constants/buy-sell'; +import { ad_type } from 'Constants/floating-rate'; import { useStores } from 'Stores'; import CreateAdSummary from './create-ad-summary.jsx'; import CreateAdErrorModal from './create-ad-error-modal.jsx'; @@ -27,12 +29,11 @@ const CreateAdFormWrapper = ({ children }) => { if (isMobile()) { return {children}; } - return children; }; const CreateAdForm = () => { - const { general_store, my_ads_store, my_profile_store } = useStores(); + const { floating_rate_store, general_store, my_ads_store, my_profile_store } = useStores(); const available_balance = useUpdatingAvailableBalance(); const os = mobileOSDetect(); @@ -74,6 +75,7 @@ const CreateAdForm = () => { return () => { disposeApiErrorReaction(); my_ads_store.setApiErrorMessage(''); + floating_rate_store.setApiErrorMessage(''); }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -101,7 +103,8 @@ const CreateAdForm = () => { max_transaction: '', min_transaction: '', offer_amount: '', - price_rate: '', + payment_info: my_ads_store.payment_info, + rate_type: floating_rate_store.rate_type === ad_type.FLOAT ? '-0.01' : '', type: buy_sell.BUY, }} onSubmit={my_ads_store.handleSubmit} @@ -114,6 +117,17 @@ const CreateAdForm = () => { {({ errors, handleChange, isSubmitting, isValid, setFieldValue, touched, values }) => { const is_sell_advert = values.type === buy_sell.SELL; + const onChangeAdTypeHandler = user_input => { + setFieldValue('type', user_input); + if (floating_rate_store.rate_type === ad_type.FLOAT) { + if (user_input === buy_sell.SELL) { + setFieldValue('rate_type', '+0.01'); + } else { + setFieldValue('rate_type', '-0.01'); + } + } + }; + return (
{ {...field} className='p2p-my-ads__form-radio-group' name='type' - onToggle={event => setFieldValue('type', event.target.value)} + onToggle={event => onChangeAdTypeHandler(event.target.value)} selected={values.type} required > @@ -150,8 +164,13 @@ const CreateAdForm = () => {
@@ -198,32 +217,57 @@ const CreateAdForm = () => { /> )} - - {({ field }) => ( - - {local_currency_config.currency} - - } - onChange={e => { - my_ads_store.restrictLength(e, handleChange); - }} - required - /> - )} + + {({ field }) => + floating_rate_store.rate_type === ad_type.FLOAT ? ( + { + my_ads_store.restrictDecimalPlace(e, 2, handleChange); + }} + {...field} + /> + ) : ( + + {local_currency_config.currency} + + } + onChange={e => { + my_ads_store.restrictLength(e, handleChange); + }} + required + /> + ) + }
diff --git a/packages/p2p/src/components/my-ads/create-ad-summary.jsx b/packages/p2p/src/components/my-ads/create-ad-summary.jsx index fbf982ef9289..9e61dcfa77d4 100644 --- a/packages/p2p/src/components/my-ads/create-ad-summary.jsx +++ b/packages/p2p/src/components/my-ads/create-ad-summary.jsx @@ -5,32 +5,53 @@ import { observer } from 'mobx-react-lite'; import { Text } from '@deriv/components'; import { buy_sell } from 'Constants/buy-sell'; import { Localize } from 'Components/i18next'; +import { setDecimalPlaces } from 'Utils/format-value.js'; import { useStores } from 'Stores'; -const CreateAdSummary = ({ offer_amount, price_rate, type }) => { +const CreateAdSummary = ({ market_feed, offer_amount, price_rate, type }) => { const { general_store } = useStores(); const { currency, local_currency_config } = general_store.client; - const display_offer_amount = offer_amount ? formatMoney(currency, offer_amount, true) : ''; - const display_price_rate = price_rate ? formatMoney(local_currency_config.currency, price_rate, true) : ''; - const display_total = - offer_amount && price_rate ? formatMoney(local_currency_config.currency, offer_amount * price_rate, true) : ''; + const display_offer_amount = offer_amount + ? formatMoney(currency, offer_amount, true, setDecimalPlaces(market_feed, 6)) + : ''; + + let display_price_rate = ''; + let display_total = ''; + + if (market_feed && price_rate) { + display_price_rate = parseFloat(market_feed * (1 + price_rate / 100)); + } else if (price_rate) { + display_price_rate = price_rate; + } + + if (market_feed && offer_amount && price_rate) { + display_total = formatMoney( + local_currency_config.currency, + offer_amount * parseFloat(market_feed * (1 + price_rate / 100)), + true + ); + } else if (offer_amount && price_rate) { + display_total = formatMoney(local_currency_config.currency, offer_amount * price_rate, true); + } if (offer_amount) { - const components = []; + const components = [ + , + , + ]; const values = { target_amount: display_offer_amount, target_currency: currency }; - if (price_rate) { Object.assign(values, { local_amount: display_total, local_currency: local_currency_config.currency, - price_rate: display_price_rate, + price_rate: formatMoney(local_currency_config.currency, display_price_rate, true), }); if (type === buy_sell.BUY) { return ( @@ -39,7 +60,7 @@ const CreateAdSummary = ({ offer_amount, price_rate, type }) => { return ( @@ -75,6 +96,7 @@ const CreateAdSummary = ({ offer_amount, price_rate, type }) => { CreateAdSummary.propTypes = { offer_amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), price_rate: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + market_feed: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), type: PropTypes.string, }; diff --git a/packages/p2p/src/components/my-ads/edit-ad-form.jsx b/packages/p2p/src/components/my-ads/edit-ad-form.jsx index 4908f035e1ee..7612ce9408ba 100644 --- a/packages/p2p/src/components/my-ads/edit-ad-form.jsx +++ b/packages/p2p/src/components/my-ads/edit-ad-form.jsx @@ -93,7 +93,7 @@ const EditAdForm = () => { max_transaction: max_order_amount_display, min_transaction: min_order_amount_display, offer_amount: amount_display, - price_rate: rate_display, + rate_type: rate_display, type, }} onSubmit={my_ads_store.onClickSaveEditAd} @@ -117,7 +117,7 @@ const EditAdForm = () => {
@@ -161,13 +161,13 @@ const EditAdForm = () => { /> )} - + {({ field }) => ( { - const { general_store, my_ads_store, my_profile_store } = useStores(); + const { floating_rate_store, general_store, my_ads_store, my_profile_store } = useStores(); const { account_currency, @@ -23,6 +25,8 @@ const MyAdsRowRenderer = observer(({ row: advert, setAdvert }) => { min_order_amount_display, payment_method_names, price_display, + rate_display, + rate_type, remaining_amount, remaining_amount_display, type, @@ -33,297 +37,273 @@ const MyAdsRowRenderer = observer(({ row: advert, setAdvert }) => { const [is_popover_actions_visible, setIsPopoverActionsVisible] = React.useState(false); const amount_dealt = amount - remaining_amount; + const enable_action_point = rate_type === ad_type.FIXED && floating_rate_store.change_ad_alert; const is_buy_advert = type === buy_sell.BUY; + const display_effective_rate = + rate_type === ad_type.FIXED + ? price_display + : parseFloat(floating_rate_store.exchange_rate * (1 + rate_display / 100)); - const onClickAddPaymentMethod = () => { - if (!general_store.is_barred) { - setAdvert(advert); - my_ads_store.showQuickAddModal(advert); - } - }; - const onClickActivateDeactivate = () => + const onClickActivateDeactivate = () => { my_ads_store.onClickActivateDeactivate(id, is_advert_active, setIsAdvertActive); - const onClickDelete = () => my_ads_store.onClickDelete(id); - const onClickEdit = () => my_ads_store.onClickEdit(id); + }; + const onClickDelete = () => !general_store.is_barred && my_ads_store.onClickDelete(id); + const onClickEdit = () => !general_store.is_barred && my_ads_store.onClickEdit(id); const onMouseEnter = () => setIsPopoverActionsVisible(true); const onMouseLeave = () => setIsPopoverActionsVisible(false); React.useEffect(() => { my_profile_store.getAdvertiserPaymentMethods(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (isMobile()) { return ( - <> - -
- -
- {is_advert_active ? ( -
- -
- ) : ( -
- -
- )} -
- +
+ +
+
+ +
+
+ +
+ + } + right_hidden_component_width='18rem' + visible_component={ + + + + +
+ + -
- - } - right_hidden_component_width='18rem' - visible_component={ - - - -
- - {is_buy_advert ? ( - - ) : ( - - )} - - {/* TODO: add conditions to show/not to show warning icon */} + {enable_action_point ? (
-
- -
- + +
+ ) : ( -
-
- - {`${formatMoney(account_currency, amount_dealt, true)}`} {account_currency}  - {is_buy_advert ? localize('Bought') : localize('Sold')} - - - {amount_display} {account_currency} - -
- -
- - - - - - -
-
- - {min_order_amount_display} - {max_order_amount_display} {account_currency} - - - {price_display} {local_currency} - -
-
- {payment_method_names ? ( - payment_method_names.map((payment_method, key) => { - return ( -
- - {payment_method} - -
- ); - }) - ) : ( -
- - - - -
- )} -
-
- } - /> - - ); - } - - return ( - <> -
- - - {is_buy_advert ? ( - - ) : ( - - )} - - - {min_order_amount_display}-{max_order_amount_display} {account_currency} - - - {price_display} {local_currency} - - + )} +
+
+ + {`${formatMoney(account_currency, amount_dealt, true)}`} {account_currency}  + {is_buy_advert ? localize('Bought') : localize('Sold')} + + + {amount_display} {account_currency} + +
-
- {remaining_amount_display}/{amount_display} {account_currency} +
+ + + + + + +
+
+ + {min_order_amount_display} - {max_order_amount_display} {account_currency} + + +
+ {formatMoney(local_currency, display_effective_rate, true)} {local_currency} + {rate_type === ad_type.FLOAT && } +
+
- - -
+
{payment_method_names ? ( payment_method_names.map((payment_method, key) => { return (
- + {payment_method}
); }) ) : ( -
- - +
{ + setAdvert(advert); + my_ads_store.showQuickAddModal(advert); + }} + > + +
)}
- - - {/* TODO: add condition to show/not to show warning icon */} + + } + /> + ); + } + + return ( +
+ + + + + + {min_order_amount_display}-{max_order_amount_display} {account_currency} + + +
+ {formatMoney(local_currency, display_effective_rate, true)} {local_currency} + {rate_type === ad_type.FLOAT && } +
+
+ + +
+ {remaining_amount_display}/{amount_display} {account_currency} +
+
+ +
+ {payment_method_names ? ( + payment_method_names.map((payment_method, key) => { + return ( +
+ + {payment_method} + +
+ ); + }) + ) : ( +
{ + setAdvert(advert); + my_ads_store.showQuickAddModal(advert); + }} + > + + + + +
+ )} +
+
+ + {enable_action_point ? (
- +
+ ) : (
-
- {is_popover_actions_visible && ( -
- {is_advert_active ? ( -
- - - -
- ) : ( -
- - - -
- )} -
+ )} + + {is_popover_actions_visible && ( +
+ {is_advert_active ? ( +
- +
-
+ ) : ( +
- +
+ )} +
+ + +
- )} - -
- +
+ + + +
+
+ )} + +
); }); diff --git a/packages/p2p/src/components/my-ads/my-ads-table.jsx b/packages/p2p/src/components/my-ads/my-ads-table.jsx index ab4640ac6aba..5aad566b28bf 100644 --- a/packages/p2p/src/components/my-ads/my-ads-table.jsx +++ b/packages/p2p/src/components/my-ads/my-ads-table.jsx @@ -24,8 +24,7 @@ const getHeaders = offered_currency => [ ]; const MyAdsTable = () => { - const { general_store, my_ads_store } = useStores(); - + const { floating_rate_store, general_store, my_ads_store } = useStores(); const [selected_advert, setSelectedAdvert] = React.useState(undefined); const local_currency = general_store.client.local_currency_config.currency; @@ -33,7 +32,7 @@ const MyAdsTable = () => { my_ads_store.setAdverts([]); my_ads_store.setSelectedAdId(''); my_ads_store.loadMoreAds({ startIndex: 0 }, true); - + general_store.setP2PConfig(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -61,22 +60,31 @@ const MyAdsTable = () => { is_warn /> )} -
- - - - } - is_warn - /> -
+ {floating_rate_store.change_ad_alert && ( +
+ + {floating_rate_store.reached_target_date ? ( + + ) : ( + + )} + + } + is_warn + /> +
+ )}
{isDesktop() && ( diff --git a/packages/p2p/src/components/my-ads/my-ads.scss b/packages/p2p/src/components/my-ads/my-ads.scss index 579d7ac1e89d..0d773ccda6e6 100644 --- a/packages/p2p/src/components/my-ads/my-ads.scss +++ b/packages/p2p/src/components/my-ads/my-ads.scss @@ -6,7 +6,7 @@ @include mobile { .page-return { border-bottom: 2px solid var(--general-section-1); - margin: unset; + margin: 1rem 0; padding: 0 1.6rem 1.6rem; } @@ -190,6 +190,7 @@ position: fixed; right: 0; width: 100vw; + z-index: 4; } } } @@ -246,6 +247,11 @@ &-price { color: var(--text-profit-success); font-weight: bold; + + .display-layout { + display: flex; + flex-direction: column; + } } &-popovers { background-color: var(--general-main-1); @@ -440,6 +446,7 @@ display: flex; justify-content: unset; width: unset; + gap: 1rem; } } } diff --git a/packages/p2p/src/components/page-return/page-return.scss b/packages/p2p/src/components/page-return/page-return.scss index 8ca95bcc383b..cc76c54d5973 100644 --- a/packages/p2p/src/components/page-return/page-return.scss +++ b/packages/p2p/src/components/page-return/page-return.scss @@ -13,5 +13,8 @@ cursor: pointer; border-radius: $BORDER_RADIUS; padding-right: 0.8rem; + @include mobile { + padding-right: 1rem; + } } } diff --git a/packages/p2p/src/stores/floating-rate-store.js b/packages/p2p/src/stores/floating-rate-store.js index da14d15290c7..125b07bae69d 100644 --- a/packages/p2p/src/stores/floating-rate-store.js +++ b/packages/p2p/src/stores/floating-rate-store.js @@ -1,6 +1,7 @@ import { action, computed, observable } from 'mobx'; import { ad_type } from 'Constants/floating-rate'; import BaseStore from 'Stores/base_store'; +import ServerTime from 'Utils/server-time'; import { requestWS } from 'Utils/websocket'; export default class FloatingRateStore extends BaseStore { @@ -11,6 +12,7 @@ export default class FloatingRateStore extends BaseStore { @observable exchange_rate; @observable change_ad_alert; @observable api_error_message = ''; + @computed get rate_type() { if (this.float_rate_adverts_status === 'enabled') { @@ -19,6 +21,14 @@ export default class FloatingRateStore extends BaseStore { return ad_type.FIXED; } + @computed + get reached_target_date() { + // Ensuring the date is translated to EOD GMT without the time difference + const current_date = new Date(ServerTime.get()) || new Date(new Date().getTime()).setUTCHours(23, 59, 59, 999); + const cutoff_date = new Date(new Date(this.fixed_rate_adverts_end_date).getTime()).setUTCHours(23, 59, 59, 999); + return current_date > cutoff_date; + } + @action.bound setFixedRateAdvertStatus(fixed_rate_advert_status) { this.fixed_rate_adverts_status = fixed_rate_advert_status; @@ -29,7 +39,7 @@ export default class FloatingRateStore extends BaseStore { } @action.bound setFloatRateOffsetLimit(offset_limit) { - this.float_rate_offset_limit = parseFloat(offset_limit); + this.float_rate_offset_limit = parseFloat(offset_limit).toFixed(2); } @action.bound setFixedRateAdvertsEndDate(end_date) { @@ -46,19 +56,21 @@ export default class FloatingRateStore extends BaseStore { @action.bound setExchangeRate(fiat_currency, local_currency) { - const pay_load = { + const payload = { exchange_rates: 1, base_currency: fiat_currency, subscribe: 1, target_currency: local_currency, }; - requestWS(pay_load).then(response => { - if (!!response && response.error) { - this.setApiErrorMessage(response.error.message); - } else { - const { rates } = response.exchange_rates; - this.exchange_rate = parseFloat(rates[local_currency]); - this.setApiErrorMessage(null); + requestWS(payload).then(response => { + if (response) { + if (response.error) { + this.setApiErrorMessage(response.error.message); + } else { + const { rates } = response.exchange_rates; + this.exchange_rate = parseFloat(rates[local_currency]); + this.setApiErrorMessage(null); + } } }); } diff --git a/packages/p2p/src/stores/general-store.js b/packages/p2p/src/stores/general-store.js index eb72d8551cc8..1cfef11f6676 100644 --- a/packages/p2p/src/stores/general-store.js +++ b/packages/p2p/src/stores/general-store.js @@ -389,11 +389,16 @@ export default class GeneralStore extends BaseStore { if (!!response && response.error) { floating_rate_store.setApiErrorMessage(response.error.message); } else { - const { fixed_rate_adverts, float_rate_adverts, float_rate_offset_limit, fixed_rate_adverts_end_date, order_payment_period } = - response.website_status.p2p_config; + const { + fixed_rate_adverts, + float_rate_adverts, + float_rate_offset_limit, + fixed_rate_adverts_end_date, + order_payment_period, + } = response.website_status.p2p_config; floating_rate_store.setFixedRateAdvertStatus(fixed_rate_adverts); floating_rate_store.setFloatingRateAdvertStatus(float_rate_adverts); - floating_rate_store.setFoatRateOffsetLimit(float_rate_offset_limit); + floating_rate_store.setFloatRateOffsetLimit(float_rate_offset_limit); floating_rate_store.setFixedRateAdvertsEndDate(fixed_rate_adverts_end_date || null); floating_rate_store.setApiErrorMessage(null); this.setOrderTimeOut(order_payment_period); diff --git a/packages/p2p/src/stores/index.js b/packages/p2p/src/stores/index.js index 412444f9dada..805c441d10dd 100644 --- a/packages/p2p/src/stores/index.js +++ b/packages/p2p/src/stores/index.js @@ -2,18 +2,19 @@ import React from 'react'; import GeneralStore from './general-store'; import AdvertiserPageStore from './advertiser-page-store'; import BuySellStore from './buy-sell-store'; +import FloatingRateStore from './floating-rate-store'; import MyAdsStore from './my-ads-store'; import MyProfileStore from './my-profile-store'; import OrderStore from './order-store'; import OrderDetailsStore from './order-details-store'; import SendbirdStore from './sendbird-store'; -import FloatingRateStore from './floating-rate-store'; class RootStore { constructor() { this.general_store = new GeneralStore(this); // Leave at the top! this.advertiser_page_store = new AdvertiserPageStore(this); this.buy_sell_store = new BuySellStore(this); + this.floating_rate_store = new FloatingRateStore(this); this.my_ads_store = new MyAdsStore(this); this.my_profile_store = new MyProfileStore(this); this.order_store = new OrderStore(this); diff --git a/packages/p2p/src/stores/my-ads-store.js b/packages/p2p/src/stores/my-ads-store.js index 0cb904e21848..0a4cc5a381d1 100644 --- a/packages/p2p/src/stores/my-ads-store.js +++ b/packages/p2p/src/stores/my-ads-store.js @@ -2,9 +2,10 @@ import { action, observable } from 'mobx'; import { getDecimalPlaces } from '@deriv/shared'; import { localize } from 'Components/i18next'; import { buy_sell } from 'Constants/buy-sell'; +import { ad_type } from 'Constants/floating-rate'; import BaseStore from 'Stores/base_store'; import { countDecimalPlaces } from 'Utils/string'; -import { decimalValidator, lengthValidator, textValidator } from 'Utils/validations'; +import { decimalValidator, lengthValidator, rangeValidator, textValidator } from 'Utils/validations'; import { requestWS } from 'Utils/websocket'; export default class MyAdsStore extends BaseStore { @@ -33,14 +34,16 @@ export default class MyAdsStore extends BaseStore { @observable is_quick_add_modal_open = false; @observable is_table_loading = false; @observable is_loading = false; + @observable is_switch_modal_open = false; @observable item_offset = 0; @observable p2p_advert_information = {}; + @observable show_ad_form = false; @observable selected_ad_id = ''; @observable selected_advert = null; @observable should_show_add_payment_method = false; @observable should_show_add_payment_method_modal = false; - @observable show_ad_form = false; @observable show_edit_ad_form = false; + @observable should_switch_ad_rate = false; @observable update_payment_methods_error_message = ''; payment_method_ids = []; @@ -133,7 +136,8 @@ export default class MyAdsStore extends BaseStore { amount: Number(values.offer_amount), max_order_amount: Number(values.max_transaction), min_order_amount: Number(values.min_transaction), - rate: Number(values.price_rate), + rate_type: this.root_store.floating_rate_store.rate_type, + rate: Number(values.rate_type), ...(this.payment_method_names.length > 0 && !is_sell_ad ? { payment_method_names: this.payment_method_names } : {}), @@ -252,7 +256,7 @@ export default class MyAdsStore extends BaseStore { id: this.selected_ad_id, max_order_amount: Number(values.max_transaction), min_order_amount: Number(values.min_transaction), - rate: Number(values.price_rate), + rate: Number(values.rate_type), ...(this.payment_method_names.length > 0 && !is_sell_ad ? { payment_method_names: this.payment_method_names } : {}), @@ -311,20 +315,28 @@ export default class MyAdsStore extends BaseStore { this.setIsTableLoading(true); this.setApiErrorMessage(''); } - - const { list_item_limit } = this.root_store.general_store; - + const { floating_rate_store, general_store } = this.root_store; return new Promise(resolve => { requestWS({ p2p_advertiser_adverts: 1, offset: startIndex, - limit: list_item_limit, + limit: general_store.list_item_limit, }).then(response => { if (!response.error) { const { list } = response.p2p_advertiser_adverts; - this.setHasMoreItemsToLoad(list.length >= list_item_limit); + this.setHasMoreItemsToLoad(list.length >= general_store.list_item_limit); this.setAdverts(this.adverts.concat(list)); this.setMissingPaymentMethods(!!list.find(payment_method => !payment_method.payment_method_names)); + let should_update_ads = false; + if (floating_rate_store.rate_type === ad_type.FLOAT) { + // Check if there are any Fixed rate ads + should_update_ads = list.some(ad => ad.rate_type === ad_type.FIXED); + floating_rate_store.setChangeAdAlert(should_update_ads); + } else if (floating_rate_store.rate_type === ad_type.FIXED) { + // Check if there are any Float rate ads + should_update_ads = list.some(ad => ad.rate_type === ad_type.FLOAT); + floating_rate_store.setChangeAdAlert(should_update_ads); + } } else if (response.error.code === 'PermissionDenied') { this.root_store.general_store.setIsBlocked(true); } else { @@ -338,10 +350,9 @@ export default class MyAdsStore extends BaseStore { } @action.bound - restrictLength = (e, handleChange) => { + restrictLength = (e, handleChange, max_characters = 15) => { // typing more than 15 characters will break the layout // max doesn't disable typing, so we will use this to restrict length - const max_characters = 15; if (e.target.value.length > max_characters) { e.target.value = e.target.value.slice(0, max_characters); return; @@ -349,6 +360,18 @@ export default class MyAdsStore extends BaseStore { handleChange(e); }; + @action.bound + restrictDecimalPlace = (e, decimal_place, handleChangeCallback) => { + const pattern = new RegExp(`^[+-]?\\d*(\\.)?(\\d{1,${decimal_place}})?$`); + if (e.target.value.length > 7) { + e.target.value = e.target.value.slice(0, 7); + return; + } + if (pattern.test(e.target.value)) { + handleChangeCallback(e); + } + }; + @action.bound showQuickAddModal(advert) { this.setSelectedAdId(advert); @@ -525,6 +548,22 @@ export default class MyAdsStore extends BaseStore { this.show_edit_ad_form = show_edit_ad_form; } + @action.bound + setIsSwitchModalOpen(is_switch_modal_open, ad_id) { + this.setSelectedAdId(ad_id); + this.is_switch_modal_open = is_switch_modal_open; + } + + @action.bound + setShouldSwitchAdRateStatus(should_switch_ad_rate) { + this.should_switch_ad_rate = should_switch_ad_rate; + if (should_switch_ad_rate) { + this.setShowEditAdForm(true); + this.getAdvertInfo(); + } + this.setIsSwitchModalOpen(false, null); + } + @action.bound setUpdatePaymentMethodsErrorMessage(update_payment_methods_error_message) { this.update_payment_methods_error_message = update_payment_methods_error_message; @@ -532,6 +571,7 @@ export default class MyAdsStore extends BaseStore { @action.bound validateCreateAdForm(values) { + const { general_store, floating_rate_store } = this.root_store; const validations = { default_advert_description: [v => !v || lengthValidator(v), v => !v || textValidator(v)], max_transaction: [ @@ -540,7 +580,7 @@ export default class MyAdsStore extends BaseStore { v => v > 0 && decimalValidator(v) && - countDecimalPlaces(v) <= getDecimalPlaces(this.root_store.general_store.client.currency), + countDecimalPlaces(v) <= getDecimalPlaces(general_store.client.currency), v => (values.offer_amount ? +v <= values.offer_amount : true), v => (values.min_transaction ? +v >= values.min_transaction : true), ], @@ -550,7 +590,7 @@ export default class MyAdsStore extends BaseStore { v => v > 0 && decimalValidator(v) && - countDecimalPlaces(v) <= getDecimalPlaces(this.root_store.general_store.client.currency), + countDecimalPlaces(v) <= getDecimalPlaces(general_store.client.currency), v => (values.offer_amount ? +v <= values.offer_amount : true), v => (values.max_transaction ? +v <= values.max_transaction : true), ], @@ -561,17 +601,24 @@ export default class MyAdsStore extends BaseStore { v => v > 0 && decimalValidator(v) && - countDecimalPlaces(v) <= getDecimalPlaces(this.root_store.general_store.client.currency), + countDecimalPlaces(v) <= getDecimalPlaces(general_store.client.currency), v => (values.min_transaction ? +v >= values.min_transaction : true), v => (values.max_transaction ? +v >= values.max_transaction : true), ], - price_rate: [ + rate_type: [ v => !!v, v => !isNaN(v), v => - v > 0 && - decimalValidator(v) && - countDecimalPlaces(v) <= this.root_store.general_store.client.local_currency_config.decimal_places, + floating_rate_store.rate_type === ad_type.FIXED + ? v > 0 && + decimalValidator(v) && + countDecimalPlaces(v) <= + this.root_store.general_store.client.local_currency_config.decimal_places + : true, + v => + floating_rate_store.rate_type === ad_type.FLOAT + ? rangeValidator(parseFloat(v), this.root_store.floating_rate_store.float_rate_offset_limit) + : true, ], }; @@ -585,7 +632,11 @@ export default class MyAdsStore extends BaseStore { max_transaction: localize('Max limit'), min_transaction: localize('Min limit'), offer_amount: localize('Amount'), - price_rate: localize('Fixed rate'), + payment_info: localize('Payment instructions'), + rate_type: + this.root_store.floating_rate_store.rate_type === ad_type.FLOAT + ? localize('Floating rate') + : localize('Fixed rate'), }; const getCommonMessages = field_name => [localize('{{field_name}} is required', { field_name })]; @@ -636,6 +687,9 @@ export default class MyAdsStore extends BaseStore { localize('{{field_name}} is required', { field_name }), localize('Enter a valid amount'), localize('Enter a valid amount'), + localize("Enter a value thats's within -{{limit}}% to +{{limit}}%", { + limit: this.root_store.floating_rate_store.float_rate_offset_limit, + }), ]; const errors = {}; @@ -659,7 +713,7 @@ export default class MyAdsStore extends BaseStore { case 'min_transaction': errors[key] = getMinTransactionLimitMessages(mapped_key[key])[error_index]; break; - case 'price_rate': + case 'rate_type': errors[key] = getPriceRateMessages(mapped_key[key])[error_index]; break; default: @@ -713,7 +767,7 @@ export default class MyAdsStore extends BaseStore { // v => (values.min_transaction ? +v >= values.min_transaction : true), // v => (values.max_transaction ? +v >= values.max_transaction : true), // ], - price_rate: [ + rate_type: [ v => !!v, v => !isNaN(v), v => @@ -733,7 +787,7 @@ export default class MyAdsStore extends BaseStore { max_transaction: localize('Max limit'), min_transaction: localize('Min limit'), offer_amount: localize('Amount'), - price_rate: localize('Fixed rate'), + rate_type: localize('Fixed rate'), }; const getCommonMessages = field_name => [localize('{{field_name}} is required', { field_name })]; @@ -807,7 +861,7 @@ export default class MyAdsStore extends BaseStore { case 'min_transaction': errors[key] = getMinTransactionLimitMessages(mapped_key[key])[error_index]; break; - case 'price_rate': + case 'rate_type': errors[key] = getPriceRateMessages(mapped_key[key])[error_index]; break; default: diff --git a/packages/p2p/src/utils/format-value.js b/packages/p2p/src/utils/format-value.js new file mode 100644 index 000000000000..5ffb5dbc343e --- /dev/null +++ b/packages/p2p/src/utils/format-value.js @@ -0,0 +1,13 @@ +export const roundOffDecimal = (number, decimal_place = 2) => { + // Rounds of the digit to the specified decimal place + return parseFloat(Math.round(number * Math.pow(10, decimal_place)) / Math.pow(10, decimal_place)); +}; + +export const setDecimalPlaces = (value, expected_decimal_place) => { + // Returns the accurate number of decimal places to prevent trailing zeros + if (!value?.toString()) { + return 0; + } + const actual_decimal_place = value.toString().split('.')[1]?.length; + return actual_decimal_place > expected_decimal_place ? expected_decimal_place : actual_decimal_place; +}; diff --git a/packages/p2p/src/utils/validations.js b/packages/p2p/src/utils/validations.js index 44f197422a29..180c75fd759d 100644 --- a/packages/p2p/src/utils/validations.js +++ b/packages/p2p/src/utils/validations.js @@ -4,5 +4,8 @@ export const lengthValidator = v => v.length >= 1 && v.length <= 300; export const textValidator = v => /^[\p{L}\p{Nd}\s'.,:;()@#+/-]*$/u.test(v); +// Validates if the given value falls within the set range and returns a boolean +export const rangeValidator = (input, limit) => input >= limit * -1 && input <= limit; + // validates floating-point integers in input box that do not contain scientific notation (e, E, -, +) such as 12.2e+2 or 12.2e-2 and no negative numbers export const floatingPointValidator = v => ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', '.'].includes(v) || /^[0-9]*[.]?[0-9]+$(?:[eE\-+]*$)/.test(v); diff --git a/packages/shared/src/styles/constants.scss b/packages/shared/src/styles/constants.scss index 686fc0b7bdd2..f674dd59b436 100644 --- a/packages/shared/src/styles/constants.scss +++ b/packages/shared/src/styles/constants.scss @@ -59,6 +59,7 @@ $alpha-color-black-5: transparentize($color-black-7, 0.16); $alpha-color-black-6: transparentize($color-black-7, 0.36); $alpha-color-blue-1: transparentize($color-blue, 0.84); $alpha-color-blue-2: transparentize($color-blue-3, 0.84); +$alpha-color-blue-3: transparentize($color-blue, 0.92); $alpha-color-white-1: transparentize($color-white, 0.04); $alpha-color-white-2: transparentize($color-white, 0.84); $alpha-color-white-3: transparentize($color-white, 0.92); diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index 5a04aa61a43b..12bb2d689eac 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -89,6 +89,7 @@ --icon-dark-background: #{$color-white}; --icon-grey-background: #{$color-grey-2}; --text-status-info-blue: #{$color-blue}; + --text-hint: #{$color-black-1}; // Purchase --purchase-main-1: #{$color-green-1}; --purchase-section-1: #{$color-green-2}; @@ -162,6 +163,7 @@ // Transparentize --transparent-success: #{$alpha-color-green-1}; --transparent-info: #{$alpha-color-blue-1}; + --transparent-hint: #{$alpha-color-blue-3}; /* TODO: change to styleguide later */ // Gradient --gradient-success: #{$gradient-color-green-1}; @@ -198,6 +200,8 @@ --text-loss-danger: #{$color-red-2}; --text-red: #{$color-red}; --text-colored-background: #{$color-white}; + --text-status-info-blue: #{$color-blue}; + --text-hint: #{$color-grey}; --icon-light-background: #{$color-black-9}; --icon-dark-background: #{$color-white}; --icon-grey-background: #{$color-black-1}; @@ -247,7 +251,7 @@ --status-adjustment: #{$color-grey-1}; --status-danger: #{$color-red-2}; --status-warning: #{$color-yellow}; - --status-warning-2: #{$alpha-color-yellow-1}; + --status-warning-transparent: #{$alpha-color-yellow-1}; --status-success: #{$color-green-3}; --status-transfer: #{$color-orange}; --status-info: #{$color-blue}; @@ -255,6 +259,7 @@ // Transparentize --transparent-success: #{$alpha-color-green-2}; --transparent-info: #{$alpha-color-blue-1}; + --transparent-hint: #{$alpha-color-blue-1}; /* TODO: change to styleguide later */ // Gradient --gradient-success: #{$gradient-color-green-2};