homeflowjs 0.7.21 → 0.7.22

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 CHANGED
@@ -8,7 +8,7 @@ pipeline {
8
8
  }
9
9
 
10
10
  options {
11
- timeout(time: 6, unit: 'HOURS')
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 /var/secrets/npm/.npmrc:/root/.npmrc'
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) npm-cli-login'
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,10 @@ pipeline {
38
39
 
39
40
  stage('Publish Verification') {
40
41
  when {
41
- branch 'jenkin-publish'
42
- triggeredBy 'BitBucketPushCause'
43
- beforeInput true
42
+ anyOf {
43
+ triggeredBy 'BitBucketPushCause'
44
+ triggeredBy 'UserIdCause'
45
+ }
44
46
  }
45
47
 
46
48
  steps {
@@ -61,9 +63,11 @@ pipeline {
61
63
  }
62
64
 
63
65
  steps {
64
- sh 'set -ax; . /var/secrets/production/npm.env ; npm-cli-login'
66
+ sh 'git config --global user.email "devops@homeflow.co.uk"'
67
+ sh 'git config --global user.name "Homeflow CI"'
65
68
  sh 'yarn publish --new-version ${PUBLISH_VERSION}'
66
- sh 'git push git@bitbucket.org:homeflow_developers/homeflowjs master'
69
+ sh 'git show'
70
+ sh 'git push git@bitbucket.org:homeflow_developers/homeflowjs HEAD:master'
67
71
  }
68
72
  }
69
73
  }
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) dispatch(setSavedProperties(json.properties))
183
+ if (json) {
184
+ dispatch(setSavedProperties(json.properties));
185
+ } else {
186
+ dispatch(setSavedProperties([]));
187
+ }
184
188
  })
185
189
  .catch(() => dispatch(setSavedProperties([])));
186
190
  };
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeflowjs",
3
- "version": "0.7.21",
3
+ "version": "0.7.22",
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
  },
@@ -42,6 +42,10 @@ const SavePropertyButton = (props) => {
42
42
  );
43
43
  };
44
44
 
45
+ SavePropertyButton.defaultProps = {
46
+ savedProperties: [],
47
+ };
48
+
45
49
  const mapStateToProps = (state) => ({
46
50
  savedProperties: state.properties.savedProperties,
47
51
  });
@@ -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);
@@ -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(search);
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
- Email me matching properties:
41
- <select
42
- className={`saved-search__frequency ${selectClass}`}
43
- onChange={handleFrequencyChange}
44
- value={search.alert_frequency}
45
- >
46
- <option value="I">Immediately</option>
47
- <option value="W">Weekly</option>
48
- <option value="M">Monthly</option>
49
- <option value="N">Never</option>
50
- </select>
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
- Delete
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
- null,
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
- const ForgottenPasswordForm = ({ inputClass, buttonClass }) => {
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) setSuccess(true);
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 OPT IN to marketing communications</span>
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 OPT OUT of marketing communications</span>
58
+ <span>I wish to opt out of marketing communications</span>
59
59
  </div>
60
60
 
61
- <button type="submit" className="marketing-preferences__submit">
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="reset-password-form__submit"
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(localUser)
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 => ({