import moment from 'moment';
import isUndefined from 'lodash/isUndefined';
import Configuration from 'common/src/app/config/Configuration';
import createAction from 'redux-actions/lib/createAction';
import debugLib from 'debug';
import formValueSelector from 'redux-form/lib/formValueSelector';
import { push as historyPush } from 'react-router-redux';
import WeighInFields from '../../data/enum/FieldNames/WeighInFields';
import Pages from '../../data/enum/Pages';
import FormNames from '../../data/enum/FormNames';
import SkippingReason from '../../data/enum/SkippingReason';
import { createPath } from '../../util/routeUtils';
import { getGoals } from './goalActions';
import { getAwards } from './awardActions';
import { getProfile } from './profileActions';
import { commonTrackEvent } from '../trackingActions';
import { Category, DataLayerKeys } from '../../data/enum/Tracking';
import { WEIGH_IN_HISTORY } from '../../data/collectionPaginationViews';
import { getValue } from '../../util/injector';
import { setEntity, removeEntities } from '../entities/entityActions';
import { WEIGH_IN, USER_WEIGH_IN_HISTORY, PROFILE } from '../../data/entityTypes';
import {
GATEWAY_COMMUNITY_AUTH,
GATEWAY_COMMUNITY_V2_AUTH,
GATEWAY_COMMUNITY_V3_AUTH,
GATEWAY_CONTENT_AUTH,
} from '../../data/Injectables';
import { apiGet, apiPost, apiPatch } from './apiActions/apiRequest';
import { userIdSelector } from '../../selectors/userAccountSelectors';
import { weighInToAddSelector } from '../../selectors/userProfileSelectors';
import apiGetCollection from './apiActions/apiGetCollection';
import { userWeighInsCollectionId, userWeighInHistoryCollectionId } from '../../data/collectionIds';
import { QUERY_PARAM_USER_ID_HINT } from '../authenticationActions';
import authenticate from '../../util/auth/authenticate';
export const VALIDATE_WEIGHT = 'weighInActions/VALIDATE_WEIGHT';
const validateWeightAction = createAction(VALIDATE_WEIGHT);
export const GET_WEIGH_IN_WEEK_NUMBER = 'weighInActions/GET_WEIGH_IN_WEEK_NUMBER';
export const GET_WEIGHT_AWARDS = 'weighInActions/GET_WEIGHT_AWARDS';
export const CHANGE_MOOD = 'weighInActions/CHANGE_MOOD';
export const GET_WEIGH_IN_RESPONSE = 'weighInActions/GET_WEIGH_IN_RESPONSE';
export const GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE =
'weighInActions/GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE';
export const GET_CURRENT_WEIGH_IN = 'weighInActions/GET_CURRENT_WEIGH_IN';
export const GET_USER_WEIGH_INS = 'weighInActions/GET_USER_WEIGH_INS';
export const GET_USER_WEIGH_IN_HISTORY = 'weighInActions/GET_USER_WEIGH_IN_HISTORY';
export const GET_USER_WEIGH_IN_BY_ID = 'weighInActions/GET_USER_WEIGH_IN_BY_ID';
export const SET_LIVE_EVENT_REDIRECT = 'weighInActions/SET_LIVE_EVENT_REDIRECT';
export const SET_WEIGH_IN_TO_ADD = 'weighInActions/SET_WEIGH_IN_TO_ADD';
export const changeMoodAction = createAction(CHANGE_MOOD);
const getWeightAwardsAction = createAction(GET_WEIGHT_AWARDS);
const getCurrentWeighInAction = createAction(GET_CURRENT_WEIGH_IN);
const getWeighInResponseAction = createAction(GET_WEIGH_IN_RESPONSE);
const getPersonalizedSupportStatementsResponseAction = createAction(
GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE,
);
export const setLiveEventRedirectAction = createAction(SET_LIVE_EVENT_REDIRECT);
export const setWeighInToAddAction = createAction(SET_WEIGH_IN_TO_ADD);
// Get the list of weigh in awards
export const getWeightAwards = () => (dispatch, getState) => {
const state = getState();
const memberId = state.authentication.userInfo.sub;
const currentWeight = state.weighIn?.savedWeighIn.weight;
const weightUnit = state.profile.account.weightUnit;
return getValue(GATEWAY_COMMUNITY_AUTH)
.get(
`/profiles/${memberId}/awards`,
{
ifWeigh: currentWeight,
weightUnit,
},
{ getState },
)
.then(result => dispatch(getWeightAwardsAction(result)));
};
// Get the current weigh in week number
export const getWeighInWeekNumber = () =>
apiGet(GET_WEIGH_IN_WEEK_NUMBER, GATEWAY_COMMUNITY_AUTH, '/profiles/me/weigh-in-week');
// Validate the weight the member has entered
export const validateWeight = weight => (dispatch, getState) => {
const data = {
weight,
weighingDateUTC: getState().weighIn.weighInToAdd?.startOfUserWeekUtc,
};
// Remove the weighingDateUTC key if its value is null
if (data.weighingDateUTC === null) {
delete data.weighingDateUTC;
}
return dispatch(
apiGet(VALIDATE_WEIGHT, GATEWAY_COMMUNITY_AUTH, '/profiles/me/weigh-ins/weight', data),
).catch(result => dispatch(validateWeightAction(result)));
};
// Get weigh in response for the bucket
export const getWeighInResponse = () => (dispatch, getState) => {
const state = getState();
const savedWeighIn = state.weighIn?.savedWeighIn;
const personalFeedback = state.weighIn?.personalFeedback;
if (savedWeighIn && !personalFeedback) {
// Get the weigh in bucket
const bucket = state.weighIn?.savedWeighIn.bucket;
return getValue(GATEWAY_CONTENT_AUTH)
.get(`/weigh-in-responses/${bucket}`, null, { getState })
.then(result => dispatch(getWeighInResponseAction(result)));
}
return null;
};
// Get personalized support statement for the virtual consultant flow
export const getPersonalizedSupportStatementsResponse = () => (dispatch, getState) => {
const state = getState();
// Get the weigh in bucket
const bucket = state.weighIn?.savedWeighIn.bucket;
return getValue(GATEWAY_CONTENT_AUTH)
.get(`/personalized-support-responses/${bucket}`, null, { getState })
.then(result => dispatch(getPersonalizedSupportStatementsResponseAction(result)));
};
/**
* Gets a the user's ongoing weigh-in from this week
*
* @returns 404 when no weigh-in has been found
* @returns 200 when weigh-in has been found
*
* @description
* weight: 234, // If weight is present, it means first step is done
* emotion: 1, // If emotion and bucket are present, it means mood step is done
* bucket: 123,
* commitment: 2, // If commitment is not null, it means commitment step is done
* isFinished: false, // If isFinished is true, it means the weigh-in is completed and has been
* shared
*/
export const getCurrentWeighIn = () => async (dispatch, getState) => {
await authenticate();
const state = getState();
const savedWeighIn = state.weighIn?.savedWeighIn;
if (!savedWeighIn) {
const profileId = userIdSelector(state);
return dispatch(
apiGet(
GET_CURRENT_WEIGH_IN,
GATEWAY_COMMUNITY_V2_AUTH,
`/profiles/${profileId}/weigh-ins/current`,
),
)
.then(result => {
dispatch(getCurrentWeighInAction(result));
})
.catch(e => {
// This is not a faulty error. This message occurs only when a user don't have any weigh-in
const debug = debugLib('WeighInActions:getCurrentWeighIn');
debug(e);
});
}
return null;
};
// Get user weigh-in collection
export const getUserWeighIns = (limit = 12) => async (dispatch, getState) => {
await authenticate();
const userId = userIdSelector(getState());
return dispatch(
apiGetCollection(
GET_USER_WEIGH_INS,
GATEWAY_COMMUNITY_AUTH,
`/profiles/${userId}/weigh-ins`,
userWeighInsCollectionId({ userId }),
{
limit,
},
{
entityType: WEIGH_IN,
caching: false,
},
),
);
};
/**
* Get the users entire weigh-in history, this is designed to be temporary until we
* can provide weight loss calculations on the back-end (MEMEX-36). Until then we need to
* fetch the users entire history otherwise there are circumstances where weight-loss messaging
* will be incorrect.
*/
export const getUserFullWeighInHistory = () => async dispatch => {
const startDate = moment(Configuration.earliestJourneyStart).format();
const endDate = moment()
.endOf('month')
.format();
return dispatch(getUserWeighInHistory(startDate, endDate));
};
// Get full weigh in history passing in a start and end date
export const getUserWeighInHistory = (startDate, endDate) => async (dispatch, getState) => {
await authenticate();
const userId = userIdSelector(getState());
return dispatch(
apiGetCollection(
GET_USER_WEIGH_IN_HISTORY,
GATEWAY_COMMUNITY_AUTH,
`/profiles/${userId}/historic-weigh-ins`,
userWeighInHistoryCollectionId({ userId }),
{},
{
updatePaginationView: {
target: WEIGH_IN_HISTORY,
extend: true,
},
entityType: USER_WEIGH_IN_HISTORY,
requestData: {
startDate,
endDate,
},
},
),
);
};
const getUserWeighInByIdAction = createAction(GET_USER_WEIGH_IN_BY_ID);
export const getUserWeighInById = id => (dispatch, getState) =>
getValue(GATEWAY_COMMUNITY_AUTH)
.get(`/profiles/weigh-ins/${id}`, null, { getState })
.then(result => dispatch(getUserWeighInByIdAction(result)));
// Save the members weight
export const POST_SAVE_WEIGHT = 'weighInActions/POST_SAVE_WEIGHT';
export const saveWeight = (isValidating = false, groupAwards) => (dispatch, getState) => {
const state = getState();
const userId = userIdSelector(state);
const weighInWeek = state?.weighIn?.weekNumber;
// Form values
const currentWeightFormSelector = formValueSelector(FormNames.WI_ENTER_WEIGHT);
const weight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);
// set award defaults
let slimmerOfTheWeek = false;
let slimmerOfTheMonth = false;
let slimmerOfLastMonth = false;
if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_THE_WEEK]))
slimmerOfTheWeek = groupAwards[WeighInFields.SLIMMER_OF_THE_WEEK];
if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_THE_MONTH]))
slimmerOfTheMonth = groupAwards[WeighInFields.SLIMMER_OF_THE_MONTH];
if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_LAST_MONTH]))
slimmerOfLastMonth = groupAwards[WeighInFields.SLIMMER_OF_LAST_MONTH];
const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);
const grantedAwards = {
slimmerOfTheWeek,
slimmerOfTheMonth: alteredMonth,
};
return dispatch(
apiPost(POST_SAVE_WEIGHT, GATEWAY_COMMUNITY_V3_AUTH, `/profiles/${userId}/weigh-ins/weight`, {
weight,
generateWarning: isValidating,
...grantedAwards,
}),
)
.then(({ data: weighIn }) => {
// delete the placeholder weighIn within the history table
dispatch(removeEntities([weighInWeek.toString()], USER_WEIGH_IN_HISTORY));
dispatch(setEntity(WEIGH_IN, weighIn.id, weighIn, true));
})
.then(() => {
if (!isValidating) {
dispatch(getProfile(true));
dispatch(getUserWeighIns());
dispatch(getAwards(true, 100));
}
});
};
export const slimmerOfTheMonthFlag = (thisMonth, lastMonth) => {
let alteredMonth = 0; // not slimmer of the month nor for last month
if (thisMonth && lastMonth) {
alteredMonth = 3; // slimmer of the month for this month and previous month
} else if (!thisMonth && !lastMonth) {
alteredMonth = 0; // NOT slimmer of the month for this month or last
} else if (thisMonth) alteredMonth = 1;
else if (lastMonth) alteredMonth = 2; // slimmer of previous month
return alteredMonth;
};
export const POST_UPDATE_WEIGHT = 'weighInActions/POST_UPDATE_WEIGHT';
export const updateWeight = (isValidating = false) => (dispatch, getState) => {
const state = getState();
const userId = userIdSelector(state);
const currentWeightFormSelector = formValueSelector(FormNames.EWI_EDIT_WEIGHT);
const weight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);
const weighInId = state.weighIn?.weighInById.id;
const week = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_WEEK);
const slimmerOfTheMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_MONTH);
const slimmerOfLastMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_LAST_MONTH);
const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);
return dispatch(
apiPatch(
POST_UPDATE_WEIGHT,
GATEWAY_COMMUNITY_AUTH,
`/profiles/${userId}/weigh-ins/${weighInId}`,
{
weight,
slimmerOfTheWeek: week,
slimmerOfTheMonth: alteredMonth,
generateWarning: isValidating,
},
),
)
.then(() => {
if (!isValidating) {
dispatch(getProfile(true));
dispatch(getUserWeighIns());
dispatch(getUserFullWeighInHistory());
dispatch(getAwards(true, 100));
}
})
.catch(result => dispatch(validateWeightAction(result)));
};
export const POST_ADD_WEIGHT = 'weighInActions/POST_ADD_WEIGHT';
export const addMissingWeight = () => (dispatch, getState) => {
const state = getState();
const userId = userIdSelector(state);
const weighInToAdd = weighInToAddSelector(state);
// Form values
const currentWeightFormSelector = formValueSelector(FormNames.AWI_ADD_WEIGHT);
const inputWeight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);
const weighInMoment = state.weighIn?.weighInToAdd?.startOfUserWeekUtc;
const week = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_WEEK);
const slimmerOfTheMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_MONTH);
const slimmerOfLastMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_LAST_MONTH);
const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);
return dispatch(
apiPost(POST_ADD_WEIGHT, GATEWAY_COMMUNITY_V3_AUTH, `/profiles/${userId}/weigh-ins/weight`, {
weighingDateUTC: weighInMoment,
weight: inputWeight,
slimmerOfTheWeek: week,
slimmerOfTheMonth: alteredMonth,
}),
)
.then(() => {
// Remove placeholder entity so we don't get duplicate weigh-ins
dispatch(removeEntities([weighInToAdd.id], USER_WEIGH_IN_HISTORY));
})
.then(() => {
dispatch(getProfile(true));
dispatch(getUserWeighIns());
dispatch(getUserFullWeighInHistory());
});
};
// Save the members mood
export const POST_SAVE_MOOD = 'weighInActions/POST_SAVE_MOOD';
export const saveMood = emotion => (dispatch, getState) => {
const state = getState();
const weighInId = state.weighIn?.savedWeighIn?.id;
if (weighInId && typeof emotion !== 'undefined') {
return dispatch(
apiPost(POST_SAVE_MOOD, GATEWAY_COMMUNITY_V2_AUTH, `/profiles/weigh-ins/${weighInId}/mood`, {
emotion,
}),
).then(() =>
dispatch(
setEntity(
WEIGH_IN,
weighInId,
{
emotion,
},
true,
),
),
);
}
return null;
};
// Save the members goals
export const POST_SAVE_GOALS = 'weighInActions/POST_SAVE_GOALS';
export const saveGoals = goals => (dispatch, getState) => {
const state = getState();
const weighInId = state.weighIn?.savedWeighIn?.id;
return dispatch(
apiPost(POST_SAVE_GOALS, GATEWAY_COMMUNITY_AUTH, `/profiles/weigh-ins/${weighInId}/goals`, {
goals,
}),
)
.then(() =>
dispatch(
setEntity(
WEIGH_IN,
weighInId,
{
goals,
},
true,
),
),
)
.then(() => dispatch(getGoals(true)));
};
// Save the members commitment
export const POST_SAVE_COMMITMENT = 'weighInActions/POST_SAVE_COMMITMENT';
export const saveCommitmentAction = createAction(POST_SAVE_COMMITMENT);
export const saveCommitment = commitment => (dispatch, getState) => {
const isWarning = getState().weighIn?.hasBmiError?.parsed?.error.fields?.[0].exception?.data
?.isWarning;
const state = getState();
const weighInId = state.weighIn?.savedWeighIn?.id;
return dispatch(
apiPost(
POST_SAVE_COMMITMENT,
GATEWAY_COMMUNITY_AUTH,
`/profiles/weigh-ins/${weighInId}/commitment`,
{
weight: commitment,
acceptWarning: !!isWarning,
},
),
).then(() =>
Promise.all([
dispatch(setEntity(WEIGH_IN, weighInId, { commitment }, true)),
dispatch(saveCommitmentAction(commitment)),
]),
);
};
export const POST_ADD_COMMITMENT = 'weighInActions/POST_ADD_COMMITMENT';
export const addCommitment = weight => (dispatch, getState) => {
const isWarning = getState().weighIn?.hasBmiError?.parsed?.error.fields?.[0].exception?.data
?.isWarning;
return dispatch(
apiPost(POST_ADD_COMMITMENT, GATEWAY_COMMUNITY_AUTH, '/profiles/me/commitments', {
weight,
acceptWarning: !!isWarning,
}),
);
};
// Save the members personal note and share
export const POST_SHARE_WEIGH_IN = 'weighInActions/POST_SHARE_WEIGH_IN';
export const shareWeighIn = () => (dispatch, getState) => {
const state = getState();
const weighInId = state.weighIn?.savedWeighIn?.id;
const userId = userIdSelector(state);
// Form values
const personalNoteFormSelector = formValueSelector(FormNames.WI_PERSONAL_NOTE);
const note = personalNoteFormSelector(state, WeighInFields.PERSONAL_NOTE);
const hideProgress = personalNoteFormSelector(state, WeighInFields.HIDE_MY_PROGRESS);
return dispatch(
apiPost(POST_SHARE_WEIGH_IN, GATEWAY_COMMUNITY_AUTH, `/profiles/weigh-ins/${weighInId}/share`, {
note,
hideProgress,
}),
)
.then(() =>
dispatch(
setEntity(
WEIGH_IN,
weighInId,
{
note,
hideProgress,
},
true,
),
),
)
.then(() => dispatch(getCurrentWeighIn()))
.then(() => dispatch(setEntity(PROFILE, userId, { isCurrentWeighInFinished: true }, true)))
.then(() => {
const { url, groupId } = state.weighIn?.liveEventRedirect || {};
if (url && url !== '' && url.length > 0) {
dispatch(
commonTrackEvent(null, {
[DataLayerKeys.CONTENT_GROUP_NAME]: Category.LIVE_EVENT,
[DataLayerKeys.LIVE_EVENT_GROUP_ID]: groupId,
[DataLayerKeys.LIVE_EVENT_BUTTON]: 'Live Event: Join Event',
}),
);
window.location.href = url;
}
});
};
export const POST_SAVE_SKIPPINGREASON = 'weighInActions/POST_SAVE_SKIPPINGREASON';
export const saveSkippingReason = (reason = null) => (dispatch, getState) => {
const state = getState();
const userId = userIdSelector(state);
let skippingReason;
if (reason) {
skippingReason = reason;
} else {
// Form values
const currentWeightFormSelector = formValueSelector(FormNames.WI_SKIP);
skippingReason = currentWeightFormSelector(state, WeighInFields.SKIPPING_REASON);
}
return dispatch(
apiPost(
POST_SAVE_SKIPPINGREASON,
GATEWAY_COMMUNITY_V3_AUTH,
`/profiles/${userId}/weigh-ins/weight`,
{
skippingReason,
},
),
)
.then(({ data: weighIn }) => {
dispatch(setEntity(WEIGH_IN, weighIn.id, weighIn, true));
// Retrieve the new weigh-in date
})
.then(() => dispatch(getProfile(true)));
};
export const SKIP_WEIGH_IN = 'weighInActions/SKIP_WEIGH_IN';
export const skipWeighIn = () => dispatch => {
// Dispatch only for tracking doesn't make use of a reducer
dispatch(createAction(SKIP_WEIGH_IN, null, null)({}));
return dispatch(saveSkippingReason(SkippingReason.NOT_REQUESTED)).then(
historyPush(Pages.WEIGHIN_SKIPPED),
);
};
export const setLiveEventRedirectUrl = containerId => (dispatch, getState) => {
const state = getState();
const liveEventHost = state.config.environmentConfig.web.live.host;
const userId = userIdSelector(state);
const liveEventUrl = `${liveEventHost}${createPath(Pages.LIVE_GROUP, {
groupId: containerId,
})}?${QUERY_PARAM_USER_ID_HINT}=${userId}`;
return dispatch(
setLiveEventRedirectAction({
url: liveEventUrl,
groupId: containerId,
}),
);
};
export const CLEAR_WEIGH_IN_BY_ID = 'weighInActions/CLEAR_WEIGH_IN_BY_ID';
export const clearWeighInByIdData = createAction(CLEAR_WEIGH_IN_BY_ID);