kitten-components
Version:
Front-end components library
550 lines (464 loc) • 18.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LoanSimulator = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _number = require('kitten/helpers/utils/number');
var _sliderWithTooltipAndPower = require('kitten/components/sliders/slider-with-tooltip-and-power');
var _textInputWithUnit = require('kitten/components/form/text-input-with-unit');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Simulator that lets users select an amount and an installment, to start
// simulating a loan.
var LoanSimulator = exports.LoanSimulator = function (_React$Component) {
_inherits(LoanSimulator, _React$Component);
function LoanSimulator(props) {
_classCallCheck(this, LoanSimulator);
var _this = _possibleConstructorReturn(this, (LoanSimulator.__proto__ || Object.getPrototypeOf(LoanSimulator)).call(this, props));
_this.state = {
amount: props.initialAmount * 1,
installmentAmount: props.initialInstallment,
dragged: !!props.initialInstallment,
touched: props.initialTouched
};
_this.handleFocus = _this.handleFocus.bind(_this);
_this.handleAmountChange = _this.handleAmountChange.bind(_this);
_this.handleEnter = _this.handleEnter.bind(_this);
_this.handleInstallmentLabelClick = _this.handleInstallmentLabelClick.bind(_this);
_this.handleInstallmentChange = _this.handleInstallmentChange.bind(_this);
_this.handleInstallmentAction = _this.handleInstallmentAction.bind(_this);
return _this;
}
_createClass(LoanSimulator, [{
key: 'handleFocus',
value: function handleFocus(e) {
this.setState({
touched: false,
installmentAmount: null
});
}
}, {
key: 'handleAmountChange',
value: function handleAmountChange(e) {
this.setState({ amount: e.target.value });
}
}, {
key: 'handleEnter',
value: function handleEnter(e) {
this.setState({ touched: true });
}
}, {
key: 'handleInstallmentLabelClick',
value: function handleInstallmentLabelClick() {
this.refs.content.focusSlider();
}
// on slider click or on grab change
}, {
key: 'handleInstallmentChange',
value: function handleInstallmentChange(value) {
this.setState({
installmentAmount: value,
dragged: true
});
}
}, {
key: 'handleInstallmentAction',
value: function handleInstallmentAction() {
this.setState({ touched: true });
}
}, {
key: 'duration',
value: function duration() {
if (this.state.installmentAmount) return Math.ceil(this.state.amount / this.state.installmentAmount);
}
}, {
key: 'commissionRate',
value: function commissionRate() {
var duration = this.duration();
for (var i = 0, len = this.props.commissionRules.length; i < len; i++) {
var rule = this.props.commissionRules[i];
if (!rule.durationMax || duration <= rule.durationMax) return rule.rate;
}
}
}, {
key: 'commissionAmount',
value: function commissionAmount() {
return this.commissionRate() * this.state.amount;
}
}, {
key: 'installmentMin',
value: function installmentMin() {
var installmentStep = this.installmentStep();
var value = this.state.amount / this.props.durationMax;
var min = Math.ceil(value / installmentStep) * installmentStep;
if (min > this.state.amount * 1) return this.state.amount;else return min;
}
}, {
key: 'installmentMax',
value: function installmentMax() {
return this.state.amount * 1;
}
}, {
key: 'installmentStep',
value: function installmentStep() {
if (this.state.installmentAmount > 1000) return 100;
if (this.state.installmentAmount > 200) return 10;
return 1;
}
}, {
key: 'error',
value: function error() {
if (!this.state.touched) return null;
return this.amountError();
}
}, {
key: 'amountError',
value: function amountError() {
if (!this.state.amount) return this.props.amountEmptyError;
if (!_number.numberUtils.isNumber(this.state.amount) || this.state.amount < this.props.amountMin || this.state.amount > this.props.amountMax) return this.props.amountOutOfBoundsError;
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(LoanSimulatorContent, _extends({
ref: 'content'
}, this.props, this.state, {
onFocus: this.handleFocus,
onAmountChange: this.handleAmountChange,
onEnter: this.handleEnter,
onInstallmentLabelClick: this.handleInstallmentLabelClick,
onInstallmentChange: this.handleInstallmentChange,
onInstallmentAction: this.handleInstallmentAction,
duration: this.duration(),
commissionAmount: this.commissionAmount(),
commissionRate: this.commissionRate(),
installmentMin: this.installmentMin(),
installmentMax: this.installmentMax(),
installmentStep: this.installmentStep(),
error: this.error(),
amountError: this.amountError()
}));
}
}]);
return LoanSimulator;
}(_react2.default.Component);
var LoanSimulatorContent = function (_React$Component2) {
_inherits(LoanSimulatorContent, _React$Component2);
function LoanSimulatorContent(props) {
_classCallCheck(this, LoanSimulatorContent);
var _this2 = _possibleConstructorReturn(this, (LoanSimulatorContent.__proto__ || Object.getPrototypeOf(LoanSimulatorContent)).call(this, props));
_this2.handleAmountKeyDown = _this2.handleAmountKeyDown.bind(_this2);
_this2.handleInstallmentChange = _this2.handleInstallmentChange.bind(_this2);
return _this2;
}
// Allow parents to focus the slider
_createClass(LoanSimulatorContent, [{
key: 'focusSlider',
value: function focusSlider() {
this.slider.focus();
}
}, {
key: 'handleAmountKeyDown',
value: function handleAmountKeyDown(e) {
// when pressing enter
if (e.keyCode == 13) {
this.focusSlider();
this.props.onEnter();
}
}
}, {
key: 'handleInstallmentChange',
value: function handleInstallmentChange(value, ratio) {
this.amount.blur();
this.props.onInstallmentChange(value, ratio);
}
}, {
key: 'toCurrency',
value: function toCurrency(cents) {
if (isNaN(cents)) return null;
return (cents / 100).toLocaleString(this.props.locale);
}
}, {
key: 'sliderIsActive',
value: function sliderIsActive() {
return !this.props.amountError && this.props.dragged && this.props.installmentAmount;
}
}, {
key: 'renderCommission',
value: function renderCommission() {
if (!this.props.displayCommission) return;
var active = this.sliderIsActive();
var amount = active ? this.toCurrency(this.props.commissionAmount * 100) : '--';
var commissionAmount = ' 0 ';
var exemptionText = null;
if (!this.props.feesExemption) {
commissionAmount = ' ' + amount + ' ';
} else {
var text = '\n (' + this.props.feesExemptionLabel + '\n ' + amount + '\n ' + this.props.currencySymbol + ')\n ';
exemptionText = _react2.default.createElement(
'span',
{ className: 'k-LoanSimulator__feesExemption' },
text
);
}
commissionAmount += ' ' + this.props.currencySymbol;
return _react2.default.createElement(
'div',
{ className: 'k-LoanSimulator__commission' },
this.props.commissionLabel,
_react2.default.createElement(
'span',
{
className: (0, _classnames2.default)({
'k-u-text--active': active,
'k-u-text--inactive': !active
})
},
commissionAmount
),
exemptionText
);
}
}, {
key: 'renderDurationError',
value: function renderDurationError() {
var _props = this.props,
duration = _props.duration,
touched = _props.touched,
requiredDurationError = _props.requiredDurationError;
if (!touched || duration > 0 || !requiredDurationError) return;
return _react2.default.createElement(
'span',
{ className: 'k-LoanSimulator__durationError' },
this.props.requiredDurationError
);
}
}, {
key: 'renderButton',
value: function renderButton() {
if (!this.props.actionLabel) return;
return _react2.default.createElement(
'div',
{ className: 'k-LoanSimulator__actions' },
_react2.default.createElement(
'button',
{ className: 'k-Button k-Button--helium k-Button--big' },
this.props.actionLabel
)
);
}
}, {
key: 'render',
value: function render() {
var _this3 = this;
var _props2 = this.props,
label = _props2.label,
dragged = _props2.dragged,
touched = _props2.touched,
duration = _props2.duration,
error = _props2.error;
var amountValid = !this.props.amountError;
var showResult = !error && touched && duration;
var installmentMin = amountValid ? this.props.installmentMin : 0;
var installmentMax = amountValid ? this.props.installmentMax : 0;
var installmentAmount = amountValid ? this.props.installmentAmount : 0;
var installmentPercentage = amountValid ? this.props.installmentPercentage : 0;
var errorClass = void 0,
errorTag = void 0,
tooltipClass = void 0,
tooltipText = void 0;
if (error) {
errorClass = 'is-error';
errorTag = _react2.default.createElement(
'p',
{ className: 'k-LoanSimulator__amount__error' },
error
);
}
if (this.sliderIsActive()) {
var durationSymbol = duration === 1 ? this.props.durationSymbol : this.props.durationSymbolPlural;
var installmentText = '\n ' + this.toCurrency(installmentAmount * 100) + '\n ' + this.props.installmentSymbol + '\n ';
var durationText = '\n ' + this.props.durationText + '\n ' + duration + '\n ' + durationSymbol + '\n ';
tooltipClass = null;
tooltipText = [_react2.default.createElement(
'div',
{ key: '1', className: 'k-LoanSimulator__installment' },
installmentText
), _react2.default.createElement(
'div',
{ key: '2', className: 'k-LoanSimulator__duration' },
durationText
)];
} else {
tooltipClass = 'is-inactive';
tooltipText = this.props.sliderPlaceholder;
}
var durationInput = void 0;
if (this.props.durationName) durationInput = _react2.default.createElement('input', {
type: 'hidden',
name: this.props.durationName,
value: this.props.duration || ''
});
return _react2.default.createElement(
'div',
{ className: (0, _classnames2.default)('k-LoanSimulator', errorClass) },
_react2.default.createElement(
'div',
{ className: 'k-LoanSimulator__amount' },
_react2.default.createElement(
'label',
{
className: 'k-Label k-LoanSimulator__label',
htmlFor: 'loan-simulator-amount'
},
this.props.amountLabel
),
_react2.default.createElement(_textInputWithUnit.TextInputWithUnit, {
ref: function ref(input) {
return _this3.amount = input;
},
error: error,
id: 'loan-simulator-amount',
name: this.props.amountName,
type: 'number',
min: this.props.amountMin,
max: this.props.amountMax,
defaultValue: this.props.initialAmount,
onFocus: this.props.onFocus,
onChange: this.props.onAmountChange,
onKeyDown: this.props.onAmountKeyDown,
placeholder: this.props.amountPlaceholder,
unit: this.props.currencySymbol
}),
errorTag
),
_react2.default.createElement(
'div',
null,
_react2.default.createElement(
'label',
{
className: 'k-Label k-LoanSimulator__label',
onClick: this.props.onInstallmentLabelClick
},
this.props.installmentLabel
),
_react2.default.createElement(_sliderWithTooltipAndPower.SliderWithTooltipAndPower, {
ref: function ref(input) {
return _this3.slider = input;
},
step: this.props.installmentStep,
min: installmentMin,
max: installmentMax,
power: 2,
name: this.props.installmentName,
value: installmentAmount,
onChange: this.handleInstallmentChange,
onAction: this.props.onInstallmentAction,
tooltipClass: tooltipClass,
tooltipText: tooltipText
}),
this.renderCommission(),
durationInput,
this.renderDurationError()
),
this.renderButton()
);
}
}]);
return LoanSimulatorContent;
}(_react2.default.Component);
LoanSimulator.propTypes = {
// Label for amount input
amountLabel: _propTypes2.default.string,
// Name attribute for the amount input (if needed)
amountName: _propTypes2.default.string,
// Name attribute for the hidden installment input (if needed)
installmentName: _propTypes2.default.string,
// Name attribute for the hidden duration input (if needed)
durationName: _propTypes2.default.string,
// Placeholder for amount input
amountPlaceholder: _propTypes2.default.string,
// Bounds for accepted amount
amountMin: _propTypes2.default.number,
amountMax: _propTypes2.default.number,
// Default amount
initialAmount: _propTypes2.default.number,
// Set this to true to show errors on first use
initialTouched: _propTypes2.default.bool,
// Error text when the amount is empty or non-numerical
amountEmptyError: _propTypes2.default.string,
// Error text when the amount is over or under the min and max
amountOutOfBoundsError: _propTypes2.default.string,
// Error text when the duration has not been set
requiredDurationError: _propTypes2.default.string,
// Display commission if requested
displayCommission: _propTypes2.default.bool,
commissionLabel: _propTypes2.default.string,
commissionRules: _propTypes2.default.array,
// Label before the slider
installmentLabel: _propTypes2.default.string,
// Text before the computed duration
durationText: _propTypes2.default.string,
// Bounds for the computed duration
durationMin: _propTypes2.default.number,
durationMax: _propTypes2.default.number,
// Duration value (months)
durationSymbol: _propTypes2.default.string,
durationSymbolPlural: _propTypes2.default.string,
// Currency
currencySymbol: _propTypes2.default.string,
// Installment
installmentSymbol: _propTypes2.default.string,
// Locale to format amounts correctly
locale: _propTypes2.default.string,
// Submit button
actionLabel: _propTypes2.default.string
};
LoanSimulator.defaultProps = {
amountLabel: 'I need',
amountPlaceholder: '',
amountMin: 1,
amountMax: 10000,
initialAmount: null,
initialTouched: false,
amountEmptyError: 'Amount cannot be empty',
amountOutOfBoundsError: 'Amount is either too big or too small',
requiredDurationError: 'Duration is required',
displayCommission: false,
feesExemption: false,
feesExemptionLabel: 'instead of',
commissionLabel: 'Fees:',
// The `commissionRules` prop has to be an array containing a `durationMax`
// rule. This will return the first `rate` which matches the rule for the
// current `duration`.
//
// Example `commissionRules` prop:
// [
// { durationMax: 12, rate: 0.3 },
// { durationMax: 20, rate: 0.2 },
// { rate: 0.1 }
// ]
commissionRules: [],
installmentLabel: "I'd like to reimburse",
initialInstallment: null,
durationText: 'during',
durationMin: 1,
durationMax: 36,
durationSymbol: 'month',
durationSymbolPlural: 'months',
currencySymbol: '$',
installmentSymbol: '$/month',
locale: 'en',
actionLabel: null
// DEPRECATED: do not use default export.
};exports.default = LoanSimulator;