homeflowjs 1.0.46 → 1.0.48

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.
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
3
3
 
4
4
  import InstantValuation from '../instant-valuation/index';
5
5
  import store from '../store';
6
- import initCookieConsent from '../cookie-consent/cookie-consent';
6
+ import { initCookieConsent } from '../cookie-consent';
7
7
  import notify from './notify';
8
8
  import antiCSRF from './anti-csrf';
9
9
  import recaptcha from './recaptcha';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Retrieves the cookie consent information from the document cookies.
3
+ *
4
+ * @returns {Object} An object containing the action and categories of the cookie consent.
5
+ * @returns {string|null} return.action - The action taken for cookie consent ("accept", "reject", or null if not available).
6
+ * @returns {string[]} return.categories - An array of categories for which consent is given. Possible values are:
7
+ * "unclassified", "performance", "functionality", "targeting", "marketing".
8
+ * If no categories are found, an empty array is returned.
9
+ */
10
+ const getCookieConsent = () => {
11
+ const cookieString = document.cookie.split('; ').find(row => row.startsWith('CookieScriptConsent='));
12
+ if (!cookieString) return { action: null, categories: [] };
13
+
14
+ const cookieValue = cookieString.replace('CookieScriptConsent=', '');
15
+ try {
16
+ const cookieObj = JSON.parse(decodeURIComponent(cookieValue));
17
+ return {
18
+ action: cookieObj.action || null,
19
+ categories: cookieObj.categories ? JSON.parse(cookieObj.categories) : [],
20
+ };
21
+ } catch (e) {
22
+ console.error('Error parsing cookie:', e);
23
+ return { action: null, categories: [] };
24
+ }
25
+ }
26
+
27
+ export default getCookieConsent;
@@ -0,0 +1,7 @@
1
+ import initCookieConsent from './cookie-consent';
2
+ import getCookieConsent from './get-cookie-consent';
3
+
4
+ export {
5
+ initCookieConsent,
6
+ getCookieConsent,
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeflowjs",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
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);