homeflowjs 1.0.47 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,7 +81,7 @@ export const createUser = (payload, recaptchaData = {}) => (dispatch, getState)
81
81
 
82
82
  let promises = [];
83
83
 
84
- if (serializedSavedProperties) {
84
+ if (serializedSavedProperties && serializedSavedProperties !== '[]') {
85
85
  const localSavedProperties = JSON.parse(serializedSavedProperties);
86
86
  const savedPropertyIDs = localSavedProperties.map((property) => property.property_id).join(',');
87
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeflowjs",
3
- "version": "1.0.47",
3
+ "version": "1.0.49",
4
4
  "sideEffects": [
5
5
  "modal/**/*",
6
6
  "user/default-profile/**/*",
@@ -9,6 +9,7 @@
9
9
  "properties/property-map/**/*",
10
10
  "properties/property-streetview/**/*",
11
11
  "properties/stamp-duty-calculator/**/*",
12
+ "search/range-controller/**/*",
12
13
  "properties/property-results-pagination/**/*"
13
14
  ],
14
15
  "description": "JavaScript toolkit for Homeflow themes",
@@ -36,6 +37,7 @@
36
37
  "react-dropdown": "^1.9.2",
37
38
  "react-leaflet": "^3.1.0",
38
39
  "react-leaflet-google-layer": "^2.0.3",
40
+ "react-range": "^1.10.0",
39
41
  "react-transition-group": "^4.4.1",
40
42
  "redux-devtools-extension": "^2.13.8",
41
43
  "redux-logger": "^3.0.6",
@@ -0,0 +1,3 @@
1
+ import RentYieldCalculator from './rent-yield-calculator';
2
+
3
+ export default RentYieldCalculator;
@@ -0,0 +1,187 @@
1
+ import React, {
2
+ memo, useState, useEffect, useMemo,
3
+ useCallback,
4
+ } from 'react';
5
+ import PropTypes from 'prop-types';
6
+ import RangeInput from '../search/range-input';
7
+ import {
8
+ RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
9
+ RANGE_CONTROLLER_CONFIG_PROP_TYPES,
10
+ } from '../search/range-controller/prop-types';
11
+
12
+ const initialFieldsConfigsList = [
13
+ {
14
+ name: 'purchasePrice',
15
+ title: 'Purchase Price or Current Value:',
16
+ minValue: 0,
17
+ maxValue: 5000000,
18
+ },
19
+ {
20
+ name: 'monthlyRent',
21
+ title: 'Monthly rent:',
22
+ minValue: 0,
23
+ maxValue: 10000,
24
+ },
25
+ {
26
+ name: 'annualRunningCosts',
27
+ title: 'Annual running costs:',
28
+ minValue: 0,
29
+ maxValue: 100000,
30
+ },
31
+ ];
32
+
33
+ const setInitialCalculatorValues = (fieldsConfigs) => fieldsConfigs
34
+ .reduce(
35
+ (values, fieldConfig) => ({
36
+ ...values,
37
+ [fieldConfig.name]: Number(fieldConfig.initialValue),
38
+ }),
39
+ {},
40
+ );
41
+
42
+ const getGrossYieldValue = ({ purchasePrice, monthlyRent }) => {
43
+ let grossYieldValue = null;
44
+ if (purchasePrice && monthlyRent) {
45
+ const annualRent = monthlyRent * 12;
46
+ grossYieldValue = (annualRent / purchasePrice) * 100;
47
+ grossYieldValue = `${grossYieldValue.toFixed(2)}%`;
48
+ }
49
+
50
+ return grossYieldValue;
51
+ };
52
+
53
+ const getNetYieldValue = ({ purchasePrice, monthlyRent, annualRunningCosts = 0 }) => {
54
+ let netYieldResult = null;
55
+
56
+ if (purchasePrice && monthlyRent) {
57
+ const annualRent = monthlyRent * 12;
58
+ netYieldResult = ((annualRent - annualRunningCosts) / purchasePrice) * 100;
59
+ netYieldResult = `${netYieldResult.toFixed(2)}%`;
60
+ }
61
+
62
+ return netYieldResult;
63
+ };
64
+
65
+ const RentYieldCalculator = ({
66
+ title,
67
+ className,
68
+ purchasePriceConfig,
69
+ monthlyRentConfig,
70
+ annualRunningCostsConfig,
71
+ rangeStylesConfigs,
72
+ currency,
73
+ grossYieldTitle,
74
+ netYieldTitle,
75
+ }) => {
76
+ const customFieldsConfigsList = useMemo(() => initialFieldsConfigsList
77
+ .map((fieldConfig) => {
78
+ const linkedFieldsData = {
79
+ purchasePrice: purchasePriceConfig || {},
80
+ monthlyRent: monthlyRentConfig || {},
81
+ annualRunningCosts: annualRunningCostsConfig || {},
82
+ };
83
+ const customConfig = linkedFieldsData[fieldConfig.name];
84
+
85
+ return {
86
+ ...fieldConfig,
87
+ ...customConfig,
88
+ initialValue: customConfig ? customConfig.initialValue || 0 : 0,
89
+ };
90
+ }), []);
91
+
92
+ const [formValues, setFormValues] = useState(null);
93
+
94
+ useEffect(() => {
95
+ setFormValues(
96
+ setInitialCalculatorValues(customFieldsConfigsList),
97
+ );
98
+ }, []);
99
+
100
+ const onFormDataUpdate = useCallback(({ name, digitValue }) => {
101
+ setFormValues((prevState) => ({
102
+ ...prevState,
103
+ [name]: digitValue,
104
+ }));
105
+ }, []);
106
+
107
+ const grossYieldValue = getGrossYieldValue({
108
+ purchasePrice: formValues?.purchasePrice,
109
+ monthlyRent: formValues?.monthlyRent,
110
+ });
111
+
112
+ const netYieldValue = getNetYieldValue({
113
+ purchasePrice: formValues?.purchasePrice,
114
+ monthlyRent: formValues?.monthlyRent,
115
+ annualRunningCosts: formValues?.annualRunningCosts,
116
+ });
117
+
118
+ return (
119
+ <div className={`${className} rent-yield-calculator__container`}>
120
+ {title && (
121
+ <h2 className="rent-yield-calculator__title">{title}</h2>
122
+ )}
123
+ {formValues && customFieldsConfigsList.map((config) => (
124
+ <div
125
+ key={config.name}
126
+ className="rent-yield-calculator__field-container"
127
+ >
128
+ <RangeInput
129
+ title={config.title}
130
+ name={config.name}
131
+ min={config.minValue}
132
+ max={config.maxValue}
133
+ inititalValue={config.initialValue}
134
+ rangeStylesConfigs={rangeStylesConfigs}
135
+ inputPrefixString={currency}
136
+ onRangeInputUpdate={onFormDataUpdate}
137
+ />
138
+ </div>
139
+ ))}
140
+ {(grossYieldValue && netYieldValue) && (
141
+ <div className="rent-yield-calculator__result-container">
142
+ <div className="rent-yield-calculator__result-item">
143
+ <h4 className="rent-yield-calculator__result-title">{grossYieldTitle}</h4>
144
+ <span className="rent-yield-calculator__result-value">{grossYieldValue}</span>
145
+ </div>
146
+ <div className="rent-yield-calculator__result-item">
147
+ <h4 className="rent-yield-calculator__result-title">{netYieldTitle}</h4>
148
+ <span className="rent-yield-calculator__result-value">{netYieldValue}</span>
149
+ </div>
150
+ </div>
151
+ )}
152
+ </div>
153
+ );
154
+ };
155
+
156
+ RentYieldCalculator.defaultProps = {
157
+ title: 'Rental Yield Calculator',
158
+ className: '',
159
+ purchasePriceConfig: {},
160
+ monthlyRentConfig: {},
161
+ annualRunningCostsConfig: {},
162
+ rangeStylesConfigs: RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
163
+ currency: '£',
164
+ grossYieldTitle: 'Gross Yield',
165
+ netYieldTitle: 'Net Yield',
166
+ };
167
+
168
+ const FieldConfigType = PropTypes.shape({
169
+ title: PropTypes.string,
170
+ minValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
171
+ maxValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
172
+ initialValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
173
+ });
174
+
175
+ RentYieldCalculator.propTypes = {
176
+ title: PropTypes.string,
177
+ className: PropTypes.string,
178
+ purchasePriceConfig: FieldConfigType,
179
+ monthlyRentConfig: FieldConfigType,
180
+ annualRunningCostsConfig: FieldConfigType,
181
+ rangeStylesConfigs: RANGE_CONTROLLER_CONFIG_PROP_TYPES,
182
+ currency: PropTypes.string,
183
+ grossYieldTitle: PropTypes.string,
184
+ netYieldTitle: PropTypes.string,
185
+ };
186
+
187
+ export default memo(RentYieldCalculator);
@@ -0,0 +1,125 @@
1
+ import React, {
2
+ memo, useCallback, useState, useEffect,
3
+ } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import { Range, getTrackBackground } from 'react-range';
6
+ import {
7
+ RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
8
+ RANGE_CONTROLLER_PROP_TYPES,
9
+ } from './prop-types';
10
+ import './range.scss';
11
+
12
+ const RangeController = ({
13
+ name,
14
+ min,
15
+ max,
16
+ values,
17
+ rangeStylesConfigs,
18
+ onRangeChange,
19
+ }) => {
20
+ const stylesConfig = {
21
+ ...RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
22
+ ...rangeStylesConfigs,
23
+ thumbBorderWidth: 2,
24
+ };
25
+ useEffect(() => {
26
+ const docEl = document.documentElement;
27
+ const thumbSizeValue = Number(stylesConfig.thumbSize.toString().match(/\d+/)[0]);
28
+ docEl.style.setProperty('--range-track-height', stylesConfig.trackHeight);
29
+ docEl.style.setProperty('--range-thumb-size', `${thumbSizeValue + 2}px`);
30
+ docEl.style.setProperty('--range-active-color', stylesConfig.trackActiveColor);
31
+ docEl.style.setProperty('--range-inactive-color', stylesConfig.trackInactiveColor);
32
+ }, []);
33
+
34
+ const [rangeValues, setRangeValues] = useState(values);
35
+
36
+ const onChange = useCallback((values) => {
37
+ if (onRangeChange) {
38
+ onRangeChange({ name, value: values });
39
+ }
40
+
41
+ setRangeValues(values);
42
+ }, [onRangeChange]);
43
+
44
+ useEffect(() => {
45
+ const validatedValues = values;
46
+ if (validatedValues.length > 1) {
47
+ validatedValues[0] = validatedValues[0] < min ? min : validatedValues[0];
48
+ validatedValues[1] = validatedValues[1] > max ? max : validatedValues[1];
49
+ } else {
50
+ validatedValues[0] = validatedValues[0] > max ? max : validatedValues[0];
51
+ validatedValues[0] = validatedValues[0] < min ? min : validatedValues[0];
52
+ }
53
+
54
+ setRangeValues(validatedValues);
55
+ }, [values]);
56
+
57
+ const renderRangeTrack = useCallback(({ props, children }) => (
58
+ <div
59
+ className="range__track"
60
+ {...props}
61
+ style={{
62
+ ...props.style,
63
+ height: stylesConfig.trackHeight,
64
+ background: getTrackBackground({
65
+ colors: [
66
+ stylesConfig.trackActiveColor,
67
+ stylesConfig.trackInactiveColor,
68
+ ],
69
+ values,
70
+ min,
71
+ max,
72
+ }),
73
+ }}
74
+ >
75
+ {children}
76
+ </div>
77
+ ), [values]);
78
+
79
+ const renderRangeThumb = useCallback(({ props }) => {
80
+ const borderActiveColor = stylesConfig.thumbBorderColor || stylesConfig.trackActiveColor;
81
+ const borderValue = `${stylesConfig.thumbBorderWidth}px solid ${borderActiveColor}`;
82
+
83
+ return (
84
+ <div
85
+ className="range__thumb"
86
+ {...props}
87
+ style={{
88
+ ...props.style,
89
+ height: stylesConfig.thumbSize,
90
+ width: stylesConfig.thumbSize,
91
+ backgroundColor: stylesConfig.thumbColor,
92
+ border: borderValue,
93
+ borderRadius: '50%',
94
+ }}
95
+ />
96
+ );
97
+ }, []);
98
+
99
+ return (
100
+ <Range
101
+ min={min}
102
+ max={max}
103
+ values={rangeValues}
104
+ onChange={onChange}
105
+ renderTrack={renderRangeTrack}
106
+ renderThumb={renderRangeThumb}
107
+ />
108
+ );
109
+ };
110
+
111
+ RangeController.defaultProps = {
112
+ name: '',
113
+ min: 0,
114
+ values: [0],
115
+ rangeStylesConfigs: RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
116
+ onRangeChange: null,
117
+ };
118
+
119
+ RangeController.propTypes = {
120
+ values: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
121
+ onRangeChange: PropTypes.func,
122
+ ...RANGE_CONTROLLER_PROP_TYPES,
123
+ };
124
+
125
+ export default memo(RangeController);
@@ -0,0 +1,26 @@
1
+ import PropTypes from 'prop-types';
2
+
3
+ export const RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS = {
4
+ trackHeight: '4px',
5
+ trackActiveColor: '#333',
6
+ trackInactiveColor: '#c4c4c4',
7
+ thumbSize: '24px',
8
+ thumbBorderColor: '',
9
+ thumbColor: '#ffffff',
10
+ };
11
+
12
+ export const RANGE_CONTROLLER_CONFIG_PROP_TYPES = PropTypes.shape({
13
+ trackHeight: PropTypes.string,
14
+ trackActiveColor: PropTypes.string,
15
+ trackInactiveColor: PropTypes.string,
16
+ thumbSize: PropTypes.string,
17
+ thumbBorderColor: PropTypes.string,
18
+ thumbColor: PropTypes.string,
19
+ });
20
+
21
+ export const RANGE_CONTROLLER_PROP_TYPES = {
22
+ name: PropTypes.string,
23
+ min: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
24
+ max: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
25
+ rangeStylesConfigs: RANGE_CONTROLLER_CONFIG_PROP_TYPES,
26
+ };
@@ -0,0 +1,25 @@
1
+ .range__track {
2
+ margin: 0 calc(var(--range-thumb-size) / 2);
3
+ position: relative;
4
+
5
+ &:before,
6
+ &:after {
7
+ content: "";
8
+ position: absolute;
9
+ top: 0;
10
+ height: 4px;
11
+ width: var(--range-thumb-size);
12
+ border-radius: 10px;
13
+ z-index: -1;
14
+ }
15
+
16
+ &:before {
17
+ background-color: var(--range-active-color);
18
+ left: calc(-1 * var(--range-thumb-size) / 2);
19
+ }
20
+
21
+ &:after {
22
+ right: calc(-1 * var(--range-thumb-size) / 2);
23
+ background-color: var(--range-inactive-color);
24
+ }
25
+ }
@@ -0,0 +1,122 @@
1
+ import React, {
2
+ memo, useCallback, useEffect, useState,
3
+ } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import RangeController from '../range-controller';
6
+ import {
7
+ RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
8
+ RANGE_CONTROLLER_PROP_TYPES,
9
+ } from '../range-controller/prop-types';
10
+
11
+ const RangeInput = ({
12
+ title,
13
+ name,
14
+ inititalValue,
15
+ min,
16
+ max,
17
+ themePrimaryColor,
18
+ rangeStylesConfigs,
19
+ inputPrefixString,
20
+ onRangeInputUpdate,
21
+ }) => {
22
+ const [valueConfig, setValueConfig] = useState({
23
+ inputValue: inputPrefixString ? `${inputPrefixString} 0` : '0',
24
+ rangeValue: 0,
25
+ });
26
+
27
+ const onValidateValue = (value) => {
28
+ let validatedValue = value;
29
+ validatedValue = validatedValue > max ? max : validatedValue;
30
+ validatedValue = validatedValue < min ? min : validatedValue;
31
+
32
+ return validatedValue;
33
+ };
34
+
35
+ useEffect(() => {
36
+ const validatedValue = onValidateValue(Number(inititalValue));
37
+ setValueConfig({
38
+ inputValue: inputPrefixString
39
+ ? `${inputPrefixString} ${validatedValue}`
40
+ : `${validatedValue}`,
41
+ rangeValue: validatedValue,
42
+ });
43
+ }, []);
44
+
45
+ const onChange = useCallback(({ name, value }) => {
46
+ const isRangeValue = Array.isArray(value);
47
+ const currentValue = isRangeValue ? value[0] : value;
48
+ const digitValue = isRangeValue
49
+ ? currentValue
50
+ : currentValue.substring(currentValue.indexOf(inputPrefixString) + 1).trim();
51
+
52
+ if (isNaN(digitValue)) {
53
+ return;
54
+ }
55
+ const validatedValue = onValidateValue(Number(digitValue));
56
+ const inputValue = inputPrefixString
57
+ ? `${inputPrefixString} ${validatedValue}`
58
+ : `${validatedValue}`;
59
+ const rangeValue = Number(validatedValue);
60
+
61
+ setValueConfig((prevState) => ({
62
+ ...prevState,
63
+ inputValue,
64
+ rangeValue,
65
+ }));
66
+
67
+ if (onRangeInputUpdate) {
68
+ onRangeInputUpdate({
69
+ name, digitValue: rangeValue, stringValue: inputValue,
70
+ });
71
+ }
72
+ }, []);
73
+
74
+ return (
75
+ <>
76
+ <h3 className="rent-yield-calculator__field-title">{title}</h3>
77
+ <div className="rent-yield-calculator__field-wrapper">
78
+ <div className="rent-yield-calculator__field-range">
79
+ <RangeController
80
+ name={name}
81
+ min={min}
82
+ max={max}
83
+ values={[valueConfig.rangeValue]}
84
+ themePrimaryColor={themePrimaryColor}
85
+ rangeStylesConfigs={rangeStylesConfigs}
86
+ onRangeChange={onChange}
87
+ />
88
+ </div>
89
+ <input
90
+ type="text"
91
+ name={name}
92
+ className="rent-yield-calculator__field"
93
+ value={valueConfig.inputValue}
94
+ onChange={(e) => onChange(
95
+ {
96
+ name: e.currentTarget.name,
97
+ value: e.currentTarget.value,
98
+ },
99
+ )}
100
+ />
101
+ </div>
102
+ </>
103
+ );
104
+ };
105
+
106
+ RangeInput.defaultProps = {
107
+ min: 0,
108
+ inititalValue: 0,
109
+ rangeStylesConfigs: RANGE_CONTROLLER_CONFIG_DEFAULT_PROPS,
110
+ inputPrefixString: '',
111
+ onRangeInputUpdate: null,
112
+ };
113
+
114
+ RangeInput.propTypes = {
115
+ title: PropTypes.string.isRequired,
116
+ inititalValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
117
+ inputPrefixString: PropTypes.string,
118
+ onRangeInputUpdate: PropTypes.func,
119
+ ...RANGE_CONTROLLER_PROP_TYPES,
120
+ };
121
+
122
+ export default memo(RangeInput);
@@ -17,6 +17,7 @@ const UserRegisterForm = (props) => {
17
17
  doNotContact,
18
18
  marketingStatement,
19
19
  redirectHash,
20
+ withRecaptcha,
20
21
  ...otherProps
21
22
  } = props;
22
23
 
@@ -80,7 +81,7 @@ const UserRegisterForm = (props) => {
80
81
  if (validateForm()) {
81
82
  setLoading({ userRegister: true });
82
83
  setIsRequestCompleted(false);
83
- onRecaptchaRender();
84
+ withRecaptcha ? onRecaptchaRender() : onRecaptchaSubmit()
84
85
  }
85
86
  };
86
87
 
@@ -119,12 +120,14 @@ UserRegisterForm.propTypes = {
119
120
  doNotContact: PropTypes.bool,
120
121
  marketingStatement: PropTypes.string,
121
122
  redirectHash: PropTypes.string,
123
+ withRecaptcha: PropTypes.bool,
122
124
  };
123
125
 
124
126
  UserRegisterForm.defaultProps = {
125
127
  doNotContact: false,
126
128
  marketingStatement: 'Marketing preferences communications', // TODO: get generic message and all themes to supply statement.
127
129
  redirectHash: null,
130
+ withRecaptcha: true,
128
131
  };
129
132
 
130
133
  const mapStateToProps = (state) => ({