homeflowjs 0.9.16 → 0.9.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeflowjs",
3
- "version": "0.9.16",
3
+ "version": "0.9.17",
4
4
  "description": "JavaScript toolkit for Homeflow themes",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable camelcase */
1
2
  import UserActionTypes from '../actions/user.types';
2
3
 
3
4
  // currentUser is user loaded from server
@@ -5,7 +6,7 @@ import UserActionTypes from '../actions/user.types';
5
6
 
6
7
  export const INITIAL_USER_DATA = {
7
8
  address_2: null,
8
- address_html: "",
9
+ address_html: '',
9
10
  agency: { hfpe_enabled: null, name: null, email: null },
10
11
  county: null,
11
12
  custom_marketing_preferences: null,
@@ -29,6 +30,7 @@ export const INITIAL_USER_DATA = {
29
30
  user_id: null,
30
31
  password: '',
31
32
  password_confirmation: '',
33
+ marketing_preferences: {},
32
34
  };
33
35
 
34
36
  // set both currentUser and localUser to same initial data
@@ -43,7 +45,7 @@ const INITIAL_STATE = {
43
45
  userCredentials: {
44
46
  email: '',
45
47
  password: '',
46
- }
48
+ },
47
49
  };
48
50
 
49
51
  // TODO: load user from localStorage if not logged in
@@ -56,12 +58,55 @@ const userReducer = (state = INITIAL_STATE, action) => {
56
58
  currentUser: action.payload,
57
59
  };
58
60
  case UserActionTypes.SET_LOCAL_USER:
61
+ if (Object.prototype.hasOwnProperty.call(action.payload, 'opt_in_terms')) {
62
+ return {
63
+ ...state,
64
+ localUser: {
65
+ ...state.localUser,
66
+ marketing_preferences: {
67
+ ...state.localUser.marketing_preferences,
68
+ ...action.payload,
69
+ },
70
+ },
71
+ };
72
+ }
73
+ if (Object.prototype.hasOwnProperty.call(action.payload, 'opt_in_marketing_url')) {
74
+ /**
75
+ * opt_in_marketing_statement opt_in_marketing and opt_in_marketing_url belong all to
76
+ * the same object, this will update the url and value of marketing_preferences if
77
+ * opted in only, the statement will get added by the UserRegisterForm component
78
+ * since it has to be registered regardless the choosen option.
79
+ */
80
+
81
+ const { opt_in_marketing_url } = action.payload;
82
+ const marketingPreferencesObj = {
83
+ opt_in_marketing: '',
84
+ opt_in_marketing_url: '',
85
+ };
86
+
87
+ if (opt_in_marketing_url) {
88
+ marketingPreferencesObj.opt_in_marketing = 'on';
89
+
90
+ marketingPreferencesObj.opt_in_marketing_url = opt_in_marketing_url;
91
+ }
92
+
93
+ return {
94
+ ...state,
95
+ localUser: {
96
+ ...state.localUser,
97
+ marketing_preferences: {
98
+ ...state.localUser.marketing_preferences,
99
+ ...marketingPreferencesObj,
100
+ },
101
+ },
102
+ };
103
+ }
59
104
  return {
60
105
  ...state,
61
106
  localUser: {
62
107
  ...state.localUser,
63
108
  ...action.payload,
64
- },
109
+ },
65
110
  };
66
111
  case UserActionTypes.SET_USER_CREDENTIALS:
67
112
  return {
@@ -74,6 +119,6 @@ const userReducer = (state = INITIAL_STATE, action) => {
74
119
  default:
75
120
  return state;
76
121
  }
77
- }
122
+ };
78
123
 
79
124
  export default userReducer;
@@ -108,11 +108,11 @@ const PriceSelect = (props) => {
108
108
  const selectedPrice = type === 'min' ? minPrice : maxPrice;
109
109
 
110
110
  if (type === 'max' && minPrice) {
111
- prices = prices.filter(price => price > minPrice);
111
+ prices = prices.filter(price => price >= minPrice);
112
112
  }
113
113
 
114
114
  if (type === 'min' && maxPrice) {
115
- prices = prices.filter(price => price < maxPrice);
115
+ prices = prices.filter(price => price <= maxPrice);
116
116
  }
117
117
 
118
118
  if (reactSelect) {
@@ -44,7 +44,7 @@ describe('PriceSelect', () => {
44
44
  const wrapper = mount(<PriceSelect {...testProps} />);
45
45
 
46
46
  // includes the default unselected option
47
- expect(wrapper.find('option').length).toEqual(2);
47
+ expect(wrapper.find('option').length).toEqual(3);
48
48
  });
49
49
 
50
50
  it('removes higher prices for min when selected for max', () => {
@@ -60,7 +60,7 @@ describe('PriceSelect', () => {
60
60
  const wrapper = mount(<PriceSelect {...testProps} />);
61
61
 
62
62
  // includes the default unselected option
63
- expect(wrapper.find('option').length).toEqual(3);
63
+ expect(wrapper.find('option').length).toEqual(4);
64
64
  });
65
65
 
66
66
  it('calls the setSearchField prop on change', () => {
@@ -1,24 +1,49 @@
1
1
  import React, { useState } from 'react';
2
- import { connect } from 'react-redux';
2
+ import { useSelector, useDispatch } from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
+ import notify from '../../app/notify';
5
+ import { setCurrentUser } from '../../actions/user.actions';
4
6
 
5
- const MarketingPreferencesForm = ({ user, buttonClass, buttonSpanClass }) => {
7
+ const MarketingPreferencesForm = ({ buttonClass, buttonSpanClass }) => {
8
+ const user = useSelector((state) => state.user.currentUser);
6
9
  const [optInMarketing, setOptInMarketing] = useState(user.is_opted_in_to_marketing);
7
10
  const contactOption = user.is_opted_in_to_marketing ? 'OPTED IN to' : 'OPTED OUT of';
11
+ const dispatch = useDispatch();
8
12
 
9
13
  const handleChange = (e) => {
10
14
  setOptInMarketing(e.target.value === 'optin');
11
15
  };
12
16
 
13
17
  const handleSubmit = (e) => {
18
+ e.preventDefault();
19
+
20
+ if (!user.marketing_preference_id) {
21
+ notify('Please sign in to update your preferences', 'error');
22
+ return;
23
+ }
24
+
14
25
  fetch(`/marketing_preferences/${user.marketing_preference_id}`, {
15
26
  method: 'PUT',
16
- body: {
17
- opt_in_marketing_at: e.target.value,
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify({
29
+ opt_in_marketing_at: optInMarketing ? 'optin' : 'optout',
18
30
  opt_in_marketing_url: document.URL,
19
31
  user_id: user.user_id,
20
- },
21
- }).then((res) => console.log(res));
32
+ }),
33
+ }).then((res) => {
34
+ if (res.ok && (res.status === 200 || res.status === 204)) {
35
+ dispatch(setCurrentUser({
36
+ ...user,
37
+ is_opted_in_to_marketing: optInMarketing,
38
+ }));
39
+ notify('Your preferences have been successfully updated', 'success');
40
+ } else {
41
+ throw res;
42
+ }
43
+ }).catch((error) => {
44
+ notify('There was an error updating your preferences', 'error');
45
+ console.error(error);
46
+ });
22
47
  };
23
48
 
24
49
  return (
@@ -66,7 +91,6 @@ const MarketingPreferencesForm = ({ user, buttonClass, buttonSpanClass }) => {
66
91
  };
67
92
 
68
93
  MarketingPreferencesForm.propTypes = {
69
- user: PropTypes.object.isRequired,
70
94
  buttonClass: PropTypes.string,
71
95
  buttonSpanClass: PropTypes.string,
72
96
  };
@@ -76,13 +100,7 @@ MarketingPreferencesForm.defaultProps = {
76
100
  buttonSpanClass: '',
77
101
  };
78
102
 
79
- const mapStateToProps = (state) => ({
80
- user: state.user.currentUser,
81
- });
82
-
83
- export default connect(
84
- mapStateToProps,
85
- )(MarketingPreferencesForm);
103
+ export default MarketingPreferencesForm;
86
104
 
87
105
  // From C2
88
106
  // $.ajax({
@@ -6,7 +6,9 @@ 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, inputClass, buttonClass, buttonSpanClass }) => {
9
+ const ResetPasswordForm = ({
10
+ user, setCurrentUser, inputClass, buttonClass, buttonSpanClass, pattern, patternTitle,
11
+ }) => {
10
12
  const [password, setPassword] = useState('');
11
13
  const [passwordConfirmation, setPasswordConfirmation] = useState('');
12
14
  const history = useHistory();
@@ -39,6 +41,8 @@ const ResetPasswordForm = ({ user, setCurrentUser, inputClass, buttonClass, butt
39
41
  value={password}
40
42
  onChange={(e) => setPassword(e.target.value)}
41
43
  className={inputClass}
44
+ pattern={pattern}
45
+ title={patternTitle}
42
46
  />
43
47
  <input
44
48
  name="password_confirmation"
@@ -47,6 +51,8 @@ const ResetPasswordForm = ({ user, setCurrentUser, inputClass, buttonClass, butt
47
51
  value={passwordConfirmation}
48
52
  onChange={(e) => setPasswordConfirmation(e.target.value)}
49
53
  className={inputClass}
54
+ pattern={pattern}
55
+ title={patternTitle}
50
56
  />
51
57
 
52
58
  <button
@@ -65,12 +71,16 @@ ResetPasswordForm.propTypes = {
65
71
  inputClass: PropTypes.string,
66
72
  buttonClass: PropTypes.string,
67
73
  buttonSpanClass: PropTypes.string,
74
+ pattern: PropTypes.string,
75
+ patternTitle: PropTypes.string,
68
76
  };
69
77
 
70
78
  ResetPasswordForm.defaultProps = {
71
79
  inputClass: '',
72
80
  buttonClass: '',
73
81
  buttonSpanClass: '',
82
+ pattern: null,
83
+ patternTitle: null,
74
84
  };
75
85
 
76
86
  const mapStateToProps = (state) => ({
@@ -21,7 +21,10 @@ const SignOutButton = ({ signOutUser, children, ...otherProps }) => (
21
21
 
22
22
  SignOutButton.propTypes = {
23
23
  signOutUser: PropTypes.func.isRequired,
24
- children: PropTypes.string.isRequired,
24
+ children: PropTypes.oneOfType([
25
+ PropTypes.element,
26
+ PropTypes.string,
27
+ ]).isRequired,
25
28
  };
26
29
 
27
30
  const mapDispatchToProps = {
@@ -31,4 +34,4 @@ const mapDispatchToProps = {
31
34
  export default connect(
32
35
  null,
33
36
  mapDispatchToProps,
34
- )(SignOutButton)
37
+ )(SignOutButton);
@@ -1,29 +1,49 @@
1
1
  import React from 'react';
2
2
  import { connect } from 'react-redux';
3
3
 
4
+ import PropTypes from 'prop-types';
5
+
4
6
  import { setLocalUser } from '../../actions/user.actions';
5
7
 
6
- const UserInput = ({ name, value, setLocalUser, ...otherProps }) => {
8
+ const UserInput = ({
9
+ name, value, unmappedValue, setLocalUser, type, ...otherProps
10
+ }) => {
7
11
  const handleChange = (e) => {
8
- setLocalUser({ [name]: e.target.value });
12
+ let { value } = e.target;
13
+ if (type === 'checkbox') value = e.target.checked ? value : '';
14
+ setLocalUser({ [name]: value });
9
15
  };
10
16
 
11
- let type = 'text';
12
- if (name === 'password' || name === 'password_confirmation') type = 'password';
13
- if (name === 'email') type = 'email';
17
+ let inputType = type;
18
+ if (name === 'password' || name === 'password_confirmation') inputType = 'password';
19
+ if (name === 'email') inputType = 'email';
14
20
 
15
21
  return (
16
22
  <input
17
23
  id={`user-input-${name}`}
18
- type={type}
24
+ type={inputType}
19
25
  name={name}
20
- value={value}
26
+ value={unmappedValue || value}
21
27
  onChange={handleChange}
22
28
  {...otherProps}
23
29
  />
24
30
  );
25
31
  };
26
32
 
33
+ UserInput.propTypes = {
34
+ name: PropTypes.string.isRequired,
35
+ setLocalUser: PropTypes.func.isRequired,
36
+ type: PropTypes.string,
37
+ unmappedValue: PropTypes.string,
38
+ // eslint-disable-next-line react/require-default-props
39
+ value: PropTypes.any,
40
+ };
41
+
42
+ UserInput.defaultProps = {
43
+ type: 'text',
44
+ unmappedValue: null,
45
+ };
46
+
27
47
  const mapStateToProps = (state, ownProps) => ({
28
48
  value: state.user.localUser[ownProps.name],
29
49
  });
@@ -35,4 +55,4 @@ const mapDispatchToProps = {
35
55
  export default connect(
36
56
  mapStateToProps,
37
57
  mapDispatchToProps,
38
- )(UserInput)
58
+ )(UserInput);
@@ -14,6 +14,7 @@ const UserRegisterForm = (props) => {
14
14
  createUser,
15
15
  setLoading,
16
16
  doNotContact,
17
+ marketingStatement,
17
18
  ...otherProps
18
19
  } = props;
19
20
 
@@ -28,6 +29,9 @@ const UserRegisterForm = (props) => {
28
29
  e.preventDefault();
29
30
 
30
31
  const userParams = doNotContact ? { ...localUser, do_not_contact: 1 } : localUser;
32
+ userParams.marketing_preferences = {
33
+ ...localUser.marketing_preferences, opt_in_marketing_statement: marketingStatement,
34
+ };
31
35
 
32
36
  if (validateForm()) {
33
37
  setLoading({ userRegister: true });
@@ -64,13 +68,15 @@ UserRegisterForm.propTypes = {
64
68
  createUser: PropTypes.func.isRequired,
65
69
  setLoading: PropTypes.func.isRequired,
66
70
  doNotContact: PropTypes.bool,
71
+ marketingStatement: PropTypes.string,
67
72
  };
68
73
 
69
74
  UserRegisterForm.defaultProps = {
70
75
  doNotContact: false,
76
+ marketingStatement: 'Marketing preferences communications', // TODO: get generic message and all themes to supply statement.
71
77
  };
72
78
 
73
- const mapStateToProps = state => ({
79
+ const mapStateToProps = (state) => ({
74
80
  localUser: state.user.localUser,
75
81
  });
76
82