homeflowjs 0.7.21 → 0.7.25
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.
- package/Jenkinsfile +14 -8
- package/__tests__/__mocks__/property-search.mock.js +0 -0
- package/actions/properties.actions.js +5 -1
- package/actions/user.actions.js +36 -0
- package/branches/branch-map/branch-leaflet-map.component.jsx +14 -5
- package/branches/branch-map/branch-map.component.jsx +4 -2
- package/branches/branches-map/branches-map.component.jsx +21 -6
- package/package.json +1 -2
- package/properties/save-property-button/save-property-button.component.jsx +4 -0
- package/properties/sort-order-select/sort-order-select.component.jsx +2 -2
- package/reducers/search.reducer.js +2 -0
- package/search/save-search-button/save-search-button.component.jsx +3 -2
- package/search/saved-search/saved-search.component.jsx +28 -14
- package/user/forgotten-password-form/forgotten-password-form.component.jsx +27 -4
- package/user/index.js +2 -0
- package/user/marketing-preferences-form/marketing-preferences-form.component.jsx +13 -6
- package/user/reset-password-form/reset-password-form.component.jsx +14 -3
- package/user/user-register-form/user-register-form.component.jsx +16 -1
package/Jenkinsfile
CHANGED
|
@@ -8,7 +8,7 @@ pipeline {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
options {
|
|
11
|
-
|
|
11
|
+
timeout(time: 6, unit: 'HOURS')
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
agent {
|
|
@@ -17,7 +17,8 @@ pipeline {
|
|
|
17
17
|
image 'homeflowdev/rubydocker'
|
|
18
18
|
args '-v /var/run/docker.sock:/var/run/docker.sock \
|
|
19
19
|
-v /var/secrets:/var/secrets \
|
|
20
|
-
-v /
|
|
20
|
+
-v /home/deployer/.npmrc:/root/.npmrc \
|
|
21
|
+
-v /home/deployer/.ssh/:/root/.ssh'
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -25,7 +26,7 @@ pipeline {
|
|
|
25
26
|
stage('Setup') {
|
|
26
27
|
steps {
|
|
27
28
|
sh 'yarn install'
|
|
28
|
-
sh 'npm install --global $(./bin/peer-dependencies -v)
|
|
29
|
+
sh 'npm install --global $(./bin/peer-dependencies -v)'
|
|
29
30
|
sh 'for f in $(./bin/peer-dependencies); do (cd "/usr/lib/node_modules/${f}/" && yarn link); done'
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -38,9 +39,12 @@ pipeline {
|
|
|
38
39
|
|
|
39
40
|
stage('Publish Verification') {
|
|
40
41
|
when {
|
|
41
|
-
branch '
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
branch 'master'
|
|
43
|
+
|
|
44
|
+
anyOf {
|
|
45
|
+
triggeredBy 'BitBucketPushCause'
|
|
46
|
+
triggeredBy 'UserIdCause'
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
steps {
|
|
@@ -61,9 +65,11 @@ pipeline {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
steps {
|
|
64
|
-
sh '
|
|
68
|
+
sh 'git config --global user.email "devops@homeflow.co.uk"'
|
|
69
|
+
sh 'git config --global user.name "Homeflow CI"'
|
|
65
70
|
sh 'yarn publish --new-version ${PUBLISH_VERSION}'
|
|
66
|
-
sh 'git
|
|
71
|
+
sh 'git show'
|
|
72
|
+
sh 'git push git@bitbucket.org:homeflow_developers/homeflowjs HEAD:master'
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
}
|
|
File without changes
|
|
@@ -180,7 +180,11 @@ export const fetchSavedProperties = () => (dispatch) => {
|
|
|
180
180
|
fetch('/saved_properties.ljson')
|
|
181
181
|
.then((response) => response.json())
|
|
182
182
|
.then((json) => {
|
|
183
|
-
if (json)
|
|
183
|
+
if (json) {
|
|
184
|
+
dispatch(setSavedProperties(json.properties));
|
|
185
|
+
} else {
|
|
186
|
+
dispatch(setSavedProperties([]));
|
|
187
|
+
}
|
|
184
188
|
})
|
|
185
189
|
.catch(() => dispatch(setSavedProperties([])));
|
|
186
190
|
};
|
package/actions/user.actions.js
CHANGED
|
@@ -3,6 +3,8 @@ import { fetchSavedProperties, setSavedProperties } from './properties.actions';
|
|
|
3
3
|
import { fetchSavedSearches, setSavedSearches } from './search.actions';
|
|
4
4
|
import { setLoading } from './app.actions';
|
|
5
5
|
import { INITIAL_USER_DATA } from '../reducers/user.reducer';
|
|
6
|
+
|
|
7
|
+
import { buildQueryString } from '../search/property-search/property-search';
|
|
6
8
|
import { objectDiff, compact } from '../utils';
|
|
7
9
|
|
|
8
10
|
export const setCurrentUser = (payload) => ({
|
|
@@ -60,6 +62,38 @@ export const createUser = (payload) => (dispatch, getState) => {
|
|
|
60
62
|
authenticity_token: authenticityToken
|
|
61
63
|
}),
|
|
62
64
|
})
|
|
65
|
+
.then((response) => response.json())
|
|
66
|
+
.then(() => {
|
|
67
|
+
// load saved properties and searches from localStorage
|
|
68
|
+
const serializedSavedProperties = localStorage.getItem('savedProperties');
|
|
69
|
+
const serializedSavedSearches = localStorage.getItem('savedSearches');
|
|
70
|
+
|
|
71
|
+
let promises = [];
|
|
72
|
+
|
|
73
|
+
if (serializedSavedProperties) {
|
|
74
|
+
const localSavedProperties = JSON.parse(serializedSavedProperties);
|
|
75
|
+
const savedPropertyIDs = localSavedProperties.map((property) => property.property_id).join(',');
|
|
76
|
+
|
|
77
|
+
promises.push(fetch('/saved_properties.ljson', {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { 'Content-Type': 'application/json' },
|
|
80
|
+
body: JSON.stringify({ property_ids: savedPropertyIDs }),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (serializedSavedSearches) {
|
|
85
|
+
const localSavedSearches = JSON.parse(serializedSavedSearches);
|
|
86
|
+
|
|
87
|
+
localSavedSearches.forEach((search) => {
|
|
88
|
+
promises.push(fetch(`/search?${buildQueryString(search)}`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Promise.all(promises);
|
|
96
|
+
})
|
|
63
97
|
.then(() => dispatch(fetchUser()));
|
|
64
98
|
};
|
|
65
99
|
|
|
@@ -84,6 +118,8 @@ export const signInUser = (payload) => (dispatch, getState) => {
|
|
|
84
118
|
console.error('Could not sign in');
|
|
85
119
|
return { success: false };
|
|
86
120
|
}
|
|
121
|
+
|
|
122
|
+
return status;
|
|
87
123
|
});
|
|
88
124
|
};
|
|
89
125
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
2
3
|
import L from 'leaflet';
|
|
3
4
|
import { MapContainer, TileLayer, Marker } from 'react-leaflet';
|
|
4
5
|
|
|
5
|
-
const BranchLeafletMap = () => {
|
|
6
|
+
const BranchLeafletMap = ({ iconConfig }) => {
|
|
6
7
|
const branch = Homeflow.get('branch');
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const defaultIconConfig = {
|
|
9
10
|
iconRetinaUrl: '/assets/marker-icon.png',
|
|
10
11
|
iconUrl: '/assets/marker-icon.png',
|
|
11
12
|
shadowUrl: '/assets/marker-shadow.png',
|
|
12
|
-
}
|
|
13
|
+
};
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const icon = L.icon(iconConfig || defaultIconConfig);
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
18
|
<MapContainer
|
|
@@ -24,9 +25,17 @@ const BranchLeafletMap = () => {
|
|
|
24
25
|
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
|
25
26
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
26
27
|
/>
|
|
27
|
-
<Marker position={[branch.lat, branch.lng]} icon={
|
|
28
|
+
<Marker position={[branch.lat, branch.lng]} icon={icon} />
|
|
28
29
|
</MapContainer>
|
|
29
30
|
);
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
BranchLeafletMap.propTypes = {
|
|
34
|
+
iconConfig: PropTypes.object,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
BranchLeafletMap.defaultProps = {
|
|
38
|
+
iconConfig: null,
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
export default BranchLeafletMap;
|
|
@@ -59,10 +59,10 @@ class BranchMap extends React.Component {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
render() {
|
|
62
|
-
const { google } = this.props;
|
|
62
|
+
const { google, iconConfig } = this.props;
|
|
63
63
|
|
|
64
64
|
if (!google) {
|
|
65
|
-
return <LazyLeafletMap />;
|
|
65
|
+
return <LazyLeafletMap iconConfig={iconConfig} />;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return (
|
|
@@ -79,6 +79,7 @@ BranchMap.propTypes = {
|
|
|
79
79
|
disableStreetview: PropTypes.bool,
|
|
80
80
|
initGoogleMaps: PropTypes.func.isRequired,
|
|
81
81
|
google: PropTypes.bool,
|
|
82
|
+
iconConfig: PropTypes.object,
|
|
82
83
|
};
|
|
83
84
|
|
|
84
85
|
BranchMap.defaultProps = {
|
|
@@ -88,6 +89,7 @@ BranchMap.defaultProps = {
|
|
|
88
89
|
zoom: 15,
|
|
89
90
|
disableStreetview: false,
|
|
90
91
|
google: false,
|
|
92
|
+
iconConfig: null,
|
|
91
93
|
};
|
|
92
94
|
|
|
93
95
|
const mapDispatchToProps = {
|
|
@@ -12,14 +12,25 @@ import ReactLeafletGoogleLayer from 'react-leaflet-google-layer';
|
|
|
12
12
|
|
|
13
13
|
import 'leaflet/dist/leaflet.css';
|
|
14
14
|
|
|
15
|
-
const BranchesMap = ({
|
|
16
|
-
|
|
15
|
+
const BranchesMap = ({
|
|
16
|
+
CustomPopup, google, gmapsKey, iconConfig, fallbackLatLng,
|
|
17
|
+
}) => {
|
|
18
|
+
let branches = Homeflow.get('branches');
|
|
19
|
+
let showMarkers = true;
|
|
20
|
+
|
|
21
|
+
// If no branches found use fallback lat, long and set showMarkers to false
|
|
22
|
+
if (!branches) {
|
|
23
|
+
branches = [fallbackLatLng];
|
|
24
|
+
showMarkers = false;
|
|
25
|
+
}
|
|
17
26
|
|
|
18
|
-
const
|
|
27
|
+
const defaultIconConfig = {
|
|
19
28
|
iconRetinaUrl: '/assets/marker-icon.png',
|
|
20
29
|
iconUrl: '/assets/marker-icon.png',
|
|
21
30
|
shadowUrl: '/assets/marker-shadow.png',
|
|
22
|
-
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const icon = L.icon(iconConfig || defaultIconConfig);
|
|
23
34
|
|
|
24
35
|
const bounds = latLngBounds([branches[0].lat, branches[0].lng]);
|
|
25
36
|
|
|
@@ -29,7 +40,7 @@ const BranchesMap = ({ CustomPopup, google, gmapsKey }) => {
|
|
|
29
40
|
return (
|
|
30
41
|
<Marker
|
|
31
42
|
position={[branch.lat, branch.lng]}
|
|
32
|
-
icon={
|
|
43
|
+
icon={icon}
|
|
33
44
|
key={branch.branch_id}
|
|
34
45
|
>
|
|
35
46
|
{CustomPopup ? (
|
|
@@ -77,7 +88,7 @@ const BranchesMap = ({ CustomPopup, google, gmapsKey }) => {
|
|
|
77
88
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
78
89
|
/>
|
|
79
90
|
)}
|
|
80
|
-
{markers}
|
|
91
|
+
{showMarkers && markers}
|
|
81
92
|
</MapContainer>
|
|
82
93
|
);
|
|
83
94
|
};
|
|
@@ -86,12 +97,16 @@ BranchesMap.propTypes = {
|
|
|
86
97
|
google: PropTypes.bool,
|
|
87
98
|
CustomPopup: PropTypes.elementType,
|
|
88
99
|
gmapsKey: PropTypes.string,
|
|
100
|
+
iconConfig: PropTypes.object,
|
|
101
|
+
fallbackLatLng: PropTypes.object,
|
|
89
102
|
};
|
|
90
103
|
|
|
91
104
|
BranchesMap.defaultProps = {
|
|
92
105
|
google: false,
|
|
93
106
|
CustomPopup: null,
|
|
94
107
|
gmapsKey: null,
|
|
108
|
+
iconConfig: null,
|
|
109
|
+
fallbackLatLng: { lat: 51.509865, lng: -0.118092 },
|
|
95
110
|
};
|
|
96
111
|
|
|
97
112
|
const mapStateToProps = (state) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homeflowjs",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.25",
|
|
4
4
|
"description": "JavaScript toolkit for Homeflow themes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"react": "^16.14.0",
|
|
40
40
|
"react-dom": "^16.14.0",
|
|
41
41
|
"react-redux": "^7.2.1",
|
|
42
|
-
"react-router": "^5.2.0",
|
|
43
42
|
"react-router-dom": "^5.3.0",
|
|
44
43
|
"redux": "^4.0.5"
|
|
45
44
|
},
|
|
@@ -20,7 +20,7 @@ const SortOrderSelect = (props) => {
|
|
|
20
20
|
|
|
21
21
|
if (reactSelect) {
|
|
22
22
|
const handleChange = ({ value }) => {
|
|
23
|
-
const newSearch = { ...search };
|
|
23
|
+
const newSearch = { ...search, page: null };
|
|
24
24
|
newSearch.sorted = value;
|
|
25
25
|
|
|
26
26
|
propertySearch(newSearch);
|
|
@@ -49,7 +49,7 @@ const SortOrderSelect = (props) => {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const handleChange = (e) => {
|
|
52
|
-
const newSearch = { ...search };
|
|
52
|
+
const newSearch = { ...search, page: null };
|
|
53
53
|
newSearch.sorted = e.target.value;
|
|
54
54
|
|
|
55
55
|
propertySearch(newSearch);
|
|
@@ -56,6 +56,8 @@ const searchReducer = (state = INITIAL_STATE, action) => {
|
|
|
56
56
|
currentSearch: {
|
|
57
57
|
...state.currentSearch,
|
|
58
58
|
...action.payload,
|
|
59
|
+
// delete poly if user enters new search query
|
|
60
|
+
poly: action.payload.q ? null : state.currentSearch.poly,
|
|
59
61
|
},
|
|
60
62
|
};
|
|
61
63
|
case SearchActionTypes.ADD_TAG: {
|
|
@@ -9,7 +9,7 @@ const SaveSearchButton = (props) => {
|
|
|
9
9
|
const {
|
|
10
10
|
search,
|
|
11
11
|
className,
|
|
12
|
-
savedSearches,
|
|
12
|
+
savedSearches = [],
|
|
13
13
|
UnsavedComponent,
|
|
14
14
|
SavedComponent,
|
|
15
15
|
addSavedSearchAsync,
|
|
@@ -22,8 +22,9 @@ const SaveSearchButton = (props) => {
|
|
|
22
22
|
const toggleSearch = (e) => {
|
|
23
23
|
e.preventDefault();
|
|
24
24
|
|
|
25
|
+
|
|
25
26
|
if (savedSearch) {
|
|
26
|
-
removeSavedSearchAsync(
|
|
27
|
+
removeSavedSearchAsync(savedSearch);
|
|
27
28
|
notify('Saved search removed.', 'success');
|
|
28
29
|
} else {
|
|
29
30
|
addSavedSearchAsync(search);
|
|
@@ -15,6 +15,8 @@ const SavedSearch = (props) => {
|
|
|
15
15
|
selectClass,
|
|
16
16
|
visitButtonClass,
|
|
17
17
|
deleteButtonClass,
|
|
18
|
+
buttonSpanClass,
|
|
19
|
+
userLoggedIn,
|
|
18
20
|
} = props;
|
|
19
21
|
|
|
20
22
|
const visitSearch = (e) => {
|
|
@@ -37,31 +39,36 @@ const SavedSearch = (props) => {
|
|
|
37
39
|
<div className="saved-search">
|
|
38
40
|
<h4 className="saved-search__title">{generateDescription(search)}</h4>
|
|
39
41
|
<div className="saved-search__body">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
{userLoggedIn && (
|
|
43
|
+
<>
|
|
44
|
+
Email me matching properties:
|
|
45
|
+
<select
|
|
46
|
+
className={`saved-search__frequency ${selectClass}`}
|
|
47
|
+
onChange={handleFrequencyChange}
|
|
48
|
+
value={search.alert_frequency}
|
|
49
|
+
>
|
|
50
|
+
<option value="I">Immediately</option>
|
|
51
|
+
<option value="W">Weekly</option>
|
|
52
|
+
<option value="M">Monthly</option>
|
|
53
|
+
<option value="N">Never</option>
|
|
54
|
+
</select>
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
51
57
|
|
|
52
58
|
<button
|
|
53
59
|
type="button"
|
|
54
60
|
onClick={visitSearch}
|
|
55
61
|
className={visitButtonClass}
|
|
56
62
|
>
|
|
57
|
-
Visit
|
|
63
|
+
<span className={buttonSpanClass}>Visit</span>
|
|
58
64
|
</button>
|
|
65
|
+
|
|
59
66
|
<button
|
|
60
67
|
type="button"
|
|
61
68
|
onClick={removeSearch}
|
|
62
69
|
className={deleteButtonClass}
|
|
63
70
|
>
|
|
64
|
-
|
|
71
|
+
<span className={buttonSpanClass}>Remove</span>
|
|
65
72
|
</button>
|
|
66
73
|
</div>
|
|
67
74
|
</div>
|
|
@@ -75,20 +82,27 @@ SavedSearch.propTypes = {
|
|
|
75
82
|
selectClass: PropTypes.string,
|
|
76
83
|
visitButtonClass: PropTypes.string,
|
|
77
84
|
deleteButtonClass: PropTypes.string,
|
|
85
|
+
buttonSpanClass: PropTypes.string,
|
|
86
|
+
userLoggedIn: PropTypes.bool.isRequired,
|
|
78
87
|
};
|
|
79
88
|
|
|
80
89
|
SavedSearch.defaultProps = {
|
|
81
90
|
selectClass: '',
|
|
82
91
|
visitButtonClass: '',
|
|
83
92
|
deleteButtonClass: '',
|
|
93
|
+
buttonSpanClass: '',
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
const mapStateToProps = (state) => ({
|
|
97
|
+
userLoggedIn: !!state.user.currentUser.user_id,
|
|
98
|
+
});
|
|
99
|
+
|
|
86
100
|
const mapDispatchToProps = {
|
|
87
101
|
removeSavedSearchAsync,
|
|
88
102
|
updateSavedSearchAsync,
|
|
89
103
|
};
|
|
90
104
|
|
|
91
105
|
export default connect(
|
|
92
|
-
|
|
106
|
+
mapStateToProps,
|
|
93
107
|
mapDispatchToProps,
|
|
94
108
|
)(SavedSearch);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import notify from '../../app/notify';
|
|
5
|
+
|
|
6
|
+
const ForgottenPasswordForm = ({ inputClass, buttonClass, buttonSpanClass }) => {
|
|
4
7
|
const [email, setEmail] = useState('');
|
|
5
8
|
const [success, setSuccess] = useState(false);
|
|
6
9
|
|
|
@@ -15,9 +18,17 @@ const ForgottenPasswordForm = ({ inputClass, buttonClass }) => {
|
|
|
15
18
|
body: formData,
|
|
16
19
|
})
|
|
17
20
|
.then(({ status }) => {
|
|
18
|
-
if (status === 200)
|
|
21
|
+
if (status === 200) {
|
|
22
|
+
setSuccess(true);
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.finally(() => {
|
|
26
|
+
notify(
|
|
27
|
+
'If the email you have entered has a user account we will send you a forgotten password reset request now.',
|
|
28
|
+
'success',
|
|
29
|
+
{ duration: 8000 },
|
|
30
|
+
);
|
|
19
31
|
})
|
|
20
|
-
.catch((err) => console.error('Something went wrong', err));
|
|
21
32
|
};
|
|
22
33
|
|
|
23
34
|
if (success) {
|
|
@@ -44,10 +55,22 @@ const ForgottenPasswordForm = ({ inputClass, buttonClass }) => {
|
|
|
44
55
|
type="submit"
|
|
45
56
|
className={`forgotten-password-form__submit ${buttonClass}`}
|
|
46
57
|
>
|
|
47
|
-
Reset Password
|
|
58
|
+
<span className={buttonSpanClass}>Reset Password</span>
|
|
48
59
|
</button>
|
|
49
60
|
</form>
|
|
50
61
|
);
|
|
51
62
|
};
|
|
52
63
|
|
|
64
|
+
ForgottenPasswordForm.propTypes = {
|
|
65
|
+
inputClass: PropTypes.string,
|
|
66
|
+
buttonClass: PropTypes.string,
|
|
67
|
+
buttonSpanClass: PropTypes.string,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
ForgottenPasswordForm.defaultProps = {
|
|
71
|
+
inputClass: '',
|
|
72
|
+
buttonClass: '',
|
|
73
|
+
buttonSpanClass: '',
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
export default ForgottenPasswordForm;
|
package/user/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import SignInInput from './user-sign-in-form/sign-in-input.component';
|
|
|
6
6
|
import SignOutButton from './sign-out-button/sign-out-button.component';
|
|
7
7
|
import MarketingPreferencesForm from './marketing-preferences-form/marketing-preferences-form.component';
|
|
8
8
|
import ForgottenPasswordForm from './forgotten-password-form/forgotten-password-form.component';
|
|
9
|
+
import ResetPasswordForm from './reset-password-form/reset-password-form.component';
|
|
9
10
|
import UserEditForm from './user-edit-form/user-edit-form.component';
|
|
10
11
|
|
|
11
12
|
export {
|
|
@@ -18,4 +19,5 @@ export {
|
|
|
18
19
|
SignOutButton,
|
|
19
20
|
MarketingPreferencesForm,
|
|
20
21
|
ForgottenPasswordForm,
|
|
22
|
+
ResetPasswordForm,
|
|
21
23
|
};
|
|
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import { connect } from 'react-redux';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
|
|
5
|
-
const MarketingPreferencesForm = ({ user }) => {
|
|
5
|
+
const MarketingPreferencesForm = ({ user, buttonClass, buttonSpanClass }) => {
|
|
6
6
|
const [optInMarketing, setOptInMarketing] = useState(user.is_opted_in_to_marketing);
|
|
7
7
|
const contactOption = user.is_opted_in_to_marketing ? 'OPTED IN to' : 'OPTED OUT of';
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ const MarketingPreferencesForm = ({ user }) => {
|
|
|
29
29
|
<div className="marketing-preferences__description">
|
|
30
30
|
You are currently
|
|
31
31
|
{' '}
|
|
32
|
-
{contactOption}
|
|
32
|
+
{contactOption.toLowerCase()}
|
|
33
33
|
{' '}
|
|
34
34
|
marketing communications.
|
|
35
35
|
</div>
|
|
@@ -43,7 +43,7 @@ const MarketingPreferencesForm = ({ user }) => {
|
|
|
43
43
|
checked={optInMarketing}
|
|
44
44
|
onChange={handleChange}
|
|
45
45
|
/>
|
|
46
|
-
<span>I wish to
|
|
46
|
+
<span>I wish to opt in to marketing communications</span>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<div className="marketing-preferences__option">
|
|
@@ -55,11 +55,11 @@ const MarketingPreferencesForm = ({ user }) => {
|
|
|
55
55
|
checked={!optInMarketing}
|
|
56
56
|
onChange={handleChange}
|
|
57
57
|
/>
|
|
58
|
-
<span>I wish to
|
|
58
|
+
<span>I wish to opt out of marketing communications</span>
|
|
59
59
|
</div>
|
|
60
60
|
|
|
61
|
-
<button type="submit" className=
|
|
62
|
-
Update
|
|
61
|
+
<button type="submit" className={`marketing-preferences__submit ${buttonClass}`}>
|
|
62
|
+
<span className={buttonSpanClass}>Update</span>
|
|
63
63
|
</button>
|
|
64
64
|
</form>
|
|
65
65
|
);
|
|
@@ -67,6 +67,13 @@ const MarketingPreferencesForm = ({ user }) => {
|
|
|
67
67
|
|
|
68
68
|
MarketingPreferencesForm.propTypes = {
|
|
69
69
|
user: PropTypes.object.isRequired,
|
|
70
|
+
buttonClass: PropTypes.string,
|
|
71
|
+
buttonSpanClass: PropTypes.string,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
MarketingPreferencesForm.defaultProps = {
|
|
75
|
+
buttonClass: '',
|
|
76
|
+
buttonSpanClass: '',
|
|
70
77
|
};
|
|
71
78
|
|
|
72
79
|
const mapStateToProps = (state) => ({
|
|
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
|
|
6
6
|
import { setCurrentUser } from '../../actions/user.actions';
|
|
7
7
|
import notify from '../../app/notify';
|
|
8
8
|
|
|
9
|
-
const ResetPasswordForm = ({ user, setCurrentUser }) => {
|
|
9
|
+
const ResetPasswordForm = ({ user, setCurrentUser, inputClass, buttonClass, buttonSpanClass }) => {
|
|
10
10
|
const [password, setPassword] = useState('');
|
|
11
11
|
const [passwordConfirmation, setPasswordConfirmation] = useState('');
|
|
12
12
|
const history = useHistory();
|
|
@@ -38,6 +38,7 @@ const ResetPasswordForm = ({ user, setCurrentUser }) => {
|
|
|
38
38
|
placeholder="New password"
|
|
39
39
|
value={password}
|
|
40
40
|
onChange={(e) => setPassword(e.target.value)}
|
|
41
|
+
className={inputClass}
|
|
41
42
|
/>
|
|
42
43
|
<input
|
|
43
44
|
name="password_confirmation"
|
|
@@ -45,13 +46,14 @@ const ResetPasswordForm = ({ user, setCurrentUser }) => {
|
|
|
45
46
|
placeholder="Confirm new password"
|
|
46
47
|
value={passwordConfirmation}
|
|
47
48
|
onChange={(e) => setPasswordConfirmation(e.target.value)}
|
|
49
|
+
className={inputClass}
|
|
48
50
|
/>
|
|
49
51
|
|
|
50
52
|
<button
|
|
51
53
|
type="submit"
|
|
52
|
-
className=
|
|
54
|
+
className={`reset-password-form__submit ${buttonClass}`}
|
|
53
55
|
>
|
|
54
|
-
Reset Password
|
|
56
|
+
<span className={buttonSpanClass}>Reset Password</span>
|
|
55
57
|
</button>
|
|
56
58
|
</form>
|
|
57
59
|
);
|
|
@@ -60,6 +62,15 @@ const ResetPasswordForm = ({ user, setCurrentUser }) => {
|
|
|
60
62
|
ResetPasswordForm.propTypes = {
|
|
61
63
|
user: PropTypes.object.isRequired,
|
|
62
64
|
setCurrentUser: PropTypes.func.isRequired,
|
|
65
|
+
inputClass: PropTypes.string,
|
|
66
|
+
buttonClass: PropTypes.string,
|
|
67
|
+
buttonSpanClass: PropTypes.string,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
ResetPasswordForm.defaultProps = {
|
|
71
|
+
inputClass: '',
|
|
72
|
+
buttonClass: '',
|
|
73
|
+
buttonSpanClass: '',
|
|
63
74
|
};
|
|
64
75
|
|
|
65
76
|
const mapStateToProps = (state) => ({
|
|
@@ -13,6 +13,7 @@ const UserRegisterForm = (props) => {
|
|
|
13
13
|
setLocalUser,
|
|
14
14
|
createUser,
|
|
15
15
|
setLoading,
|
|
16
|
+
doNotContact,
|
|
16
17
|
...otherProps
|
|
17
18
|
} = props;
|
|
18
19
|
|
|
@@ -26,13 +27,22 @@ const UserRegisterForm = (props) => {
|
|
|
26
27
|
const handleSubmit = (e) => {
|
|
27
28
|
e.preventDefault();
|
|
28
29
|
|
|
30
|
+
const userParams = doNotContact ? { ...localUser, do_not_contact: 1 } : localUser;
|
|
31
|
+
|
|
29
32
|
if (validateForm()) {
|
|
30
33
|
setLoading({ userRegister: true });
|
|
31
34
|
|
|
32
|
-
createUser(
|
|
35
|
+
createUser(userParams)
|
|
33
36
|
.then(() => {
|
|
34
37
|
setLoading({ userRegister: false });
|
|
35
38
|
notify('You have been successfully registered!', 'success');
|
|
39
|
+
})
|
|
40
|
+
.catch(() => {
|
|
41
|
+
notify(
|
|
42
|
+
'Sorry, something went wrong. You may already have an account with this email address, if so, please try signing in instead of registering.',
|
|
43
|
+
'error',
|
|
44
|
+
{ duration: 8000 },
|
|
45
|
+
);
|
|
36
46
|
});
|
|
37
47
|
}
|
|
38
48
|
};
|
|
@@ -53,6 +63,11 @@ UserRegisterForm.propTypes = {
|
|
|
53
63
|
setLocalUser: PropTypes.func.isRequired,
|
|
54
64
|
createUser: PropTypes.func.isRequired,
|
|
55
65
|
setLoading: PropTypes.func.isRequired,
|
|
66
|
+
doNotContact: PropTypes.bool,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
UserRegisterForm.defaultProps = {
|
|
70
|
+
doNotContact: false,
|
|
56
71
|
};
|
|
57
72
|
|
|
58
73
|
const mapStateToProps = state => ({
|