\n
\n Chat\n \n {this.displayAllMessages()}\n
{\n this.messagesEnd = el;\n }}\n />\n
\n );\n }\n}\n\nexport default DisplayMessages;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nconst Textarea = (props) => {\n const {\n fieldName,\n values,\n value,\n handleChange,\n rows,\n disabled,\n focused,\n classNames,\n maxLength,\n placeholder,\n onKeyDown = () => {},\n } = props;\n\n const myValue = value || values[fieldName] || '';\n return (\n
\n );\n};\n\nTextarea.propTypes = {\n values: PropTypes.object.isRequired,\n value: PropTypes.string,\n handleChange: PropTypes.func.isRequired,\n fieldName: PropTypes.string.isRequired,\n rows: PropTypes.number,\n focused: PropTypes.bool,\n disabled: PropTypes.bool,\n classNames: PropTypes.string,\n maxLength: PropTypes.number,\n placeholder: PropTypes.string,\n onKeyDown: PropTypes.func,\n};\n\nTextarea.defaultProps = {\n rows: 2,\n disabled: false,\n focused: false,\n classNames: '',\n maxLength: 1600,\n placeholder: 'Message...',\n value: '',\n onKeyDown: () => {},\n};\n\nexport default Textarea;\n","import Textarea from './src/Textarea';\n\nexport default Textarea;\n","import axios from 'axios';\n\nconst axiosSend = async (url, file, setProgress) => {\n const config = {\n headers: {\n 'Content-Type': file.type,\n },\n timeout: 90000,\n onUploadProgress(progressEvent) {\n // console.devLog.info('Progress event:', progressEvent);\n const percentCompleted = Math.round(\n (progressEvent.loaded * 100) / progressEvent.total,\n );\n // console.devLog.info(percentCompleted);\n setProgress(percentCompleted);\n },\n };\n // console.devLog.info('Axios url:', url);\n // console.devLog.info('Axios data:', data);\n try {\n const result = await axios\n .put(url, file, config)\n .then((res) => {\n console.devLog.info('axios send result:', res);\n return res;\n })\n .catch((err) => {\n console.devLog.info('axios send error:', err);\n throw new Error(err);\n });\n\n return result;\n } catch (e) {\n console.devLog.info('Axios error:', e);\n throw new Error(e);\n }\n};\n\nexport default axiosSend;\n","import apiFetch from '../../api-fetch';\nimport Toast from '../../toast';\nimport { sendSlackNotification } from '../../functions';\nimport Sentry from '../../sentry';\nimport axiosSend from './axiosSend';\n\nconst uploadFile = async (\n file,\n setIsUploading = () => {},\n setUploadProgress = () => {},\n destinationBucket = 'MovementVideoBucket',\n) => {\n const parts = file.name.split('.');\n const fileExtension = parts[parts.length - 1].toLowerCase();\n // console.devLog.info('File extension:', fileExtension);\n\n try {\n const { filename, url } = await apiFetch({\n url: 'Client/GetUploadURL',\n method: 'GET',\n query: { fileExtension, destinationBucket },\n });\n\n try {\n // Figure out how to properly upload larger files to S3 here\n\n // https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html\n // Example: https://github.com/minio/cookbook/blob/master/docs/presigned-put-upload-via-browser.md\n\n const fetchResult = await axiosSend(url, file, setUploadProgress);\n\n // console.devLog.info('fetch result:', fetchResult);\n // Return the filename if response was successful\n if (fetchResult && fetchResult.status === 200) {\n return filename;\n }\n\n return null;\n } catch (er) {\n console.devLog.info('Error uploading file to AWS:', er);\n Toast.error('Error uploading file to AWS.');\n setIsUploading(false);\n setUploadProgress(0);\n sendSlackNotification(\n `Error uploading media in TWA chat (check sentry): ${er}`,\n );\n Sentry.captureException(er, {\n extra: {\n ...file,\n },\n });\n setIsUploading(false);\n setUploadProgress(0);\n throw new Error(er);\n }\n } catch (e) {\n console.devLog.info('Error getting upload URL:', e);\n // Toast.error('Error getting upload URL');\n Sentry.captureException(e, {\n extra: {\n ...file,\n },\n });\n throw new Error(e);\n return null;\n }\n};\n\nexport default uploadFile;\n","import Dropzone from './src/Dropzone';\n\nexport default Dropzone;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport Dropzone from 'react-dropzone';\nimport classNames from 'classnames';\n\nimport Toast from 'modules/toast';\n\nimport Loading from 'modules/loading';\n\nexport default class DropZone extends React.Component {\n static propTypes = {\n clientData: PropTypes.object.isRequired,\n handleUploadFile: PropTypes.func.isRequired,\n };\n\n state = {\n isUploading: false,\n };\n onDrop = (acceptedFiles, rejectedFiles) => {\n // Do something with files\n\n if (rejectedFiles.length > 0) {\n rejectedFiles.forEach((file) => {\n Toast.error(`Rejected file: ${file.name}`);\n });\n }\n if (acceptedFiles.length > 0) {\n const allUploadPromises = acceptedFiles.map((file) =>\n this.props.handleUploadFile(file),\n );\n\n this.setState(\n {\n isUploading: true,\n },\n async () => {\n // Call our list of promises here\n await Promise.all(allUploadPromises);\n this.setState({\n isUploading: false,\n });\n },\n );\n }\n };\n\n render() {\n const baseStyle = {\n width: '100%',\n borderWidth: 2,\n borderColor: '#666',\n borderStyle: 'dashed',\n borderRadius: 5,\n height: 100,\n display: 'flex',\n textAlign: 'center',\n alignItems: 'center',\n justifyContent: 'center',\n };\n const activeStyle = {\n borderStyle: 'solid',\n borderColor: '#6c6',\n backgroundColor: '#eee',\n };\n const rejectStyle = {\n borderStyle: 'solid',\n borderColor: '#c66',\n backgroundColor: '#eee',\n };\n\n const isUploading = {\n backgroundColor: '#c66',\n };\n\n return (\n
\n
\n {({ getRootProps, getInputProps, isDragActive, isDragReject }) => {\n let styles = { ...baseStyle };\n styles = isDragActive ? { ...styles, ...activeStyle } : styles;\n styles = isDragReject ? { ...styles, ...rejectStyle } : styles;\n styles = this.state.isUploading\n ? { ...styles, ...isUploading }\n : styles;\n return (\n \n
\n {isDragActive ? (\n
Drop files here...
\n ) : (\n
\n Try dropping some files here, or click/tap to select files\n to upload.\n
\n )}\n
\n );\n }}\n \n {this.state.isUploading && (\n
\n \n Loading...\n \n )}\n
\n );\n }\n}\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport moment from 'moment';\nimport ReactTooltip from 'react-tooltip';\nimport { doesClientHaveActiveChurnThreat } from 'modules/functions';\n\nconst ClientStatusLegend = ({ client }) => {\n const {\n isDisengaged,\n isTransfer,\n\n isNew,\n vacationUntil,\n highPriority,\n } = client;\n\n const isChurnThreat = doesClientHaveActiveChurnThreat(client.churnThreats);\n\n const isOnVacation =\n moment(vacationUntil).isValid &&\n moment(vacationUntil).isAfter(moment(), 'day');\n const legendKeys = [];\n if (isNew) {\n legendKeys.push({ initial: 'N', full: 'New' });\n }\n if (isDisengaged) {\n legendKeys.push({\n initial: 'D',\n full: 'Disengaged',\n });\n }\n if (isTransfer) {\n legendKeys.push({\n initial: 'T',\n full: 'Transfer',\n });\n }\n if (isChurnThreat) {\n legendKeys.push({\n initial: 'C',\n full: 'Churn Threat',\n });\n }\n if (highPriority) {\n legendKeys.push({\n initial: 'H',\n full: 'High Priority',\n });\n }\n if (isOnVacation) {\n legendKeys.push({\n initial: 'V',\n full: 'Vacation Mode',\n });\n }\n\n return (\n
\n \n {legendKeys.map((key) => (\n \n {key.initial}\n \n ))}\n
\n );\n};\n\nClientStatusLegend.propTypes = {\n client: PropTypes.object.isRequired,\n};\n\nexport default ClientStatusLegend;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport apiFetch from 'modules/api-fetch';\nimport Toast from 'modules/toast';\n\nexport default class MessagesClientPush extends React.Component {\n static propTypes = {\n clientData: PropTypes.object.isRequired,\n };\n\n sendPush = async () => {\n // Trigger a dialog to get the input\n const messageText = prompt(\n 'What would you like the push notification to say?',\n );\n if (messageText) {\n const config = {\n url: 'Trainer/SendPushNotification',\n method: 'GET',\n query: {\n clientId: this.props.clientData.data[0].id,\n message: messageText,\n },\n };\n try {\n await apiFetch(config);\n Toast.success('Push notification sent!');\n } catch (er) {\n Toast.error(`Error sending push: ${er}`);\n }\n }\n };\n\n render() {\n return (\n
\n );\n }\n}\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport apiFetch from 'modules/api-fetch';\nimport Toast from 'modules/toast';\n\nimport { Formik, Form } from 'formik';\n\nconst MessagesEventPrefs = (props) => {\n const { clientData } = props;\n\n const formSubmit = async (values) => {\n const clientObject = Object.assign({}, props.clientData.data[0]);\n // TODO: improve this to actually loop through an array of keys?\n const eventPrefsValues = {\n foodLog: values.foodLog,\n workout: values.workout,\n task: values.task,\n trainerTask: values.trainerTask,\n progressPhoto: values.progressPhoto,\n bodyStat: values.bodyStat,\n formCheckEntry: values.formCheckEntry,\n };\n // console.devLog.info('Event prefs values:', eventPrefsValues);\n clientObject.messageEventDisplayPrefs = JSON.stringify(eventPrefsValues);\n clientObject.chatMedium = parseInt(values.chatMedium, 10);\n // console.devLog.info('Form submit values:', values);\n // console.devLog.info('Submit formik props:', formikProps);\n // console.devLog.info('Lets see config going out yo', clientObject);\n const config = {\n url: 'Trainer/UpdateClient',\n method: 'POST',\n body: clientObject,\n };\n try {\n await apiFetch(config);\n Toast.success('Profile updated');\n clientData.refreshData(false);\n } catch (e) {\n // console.devLog.info('Messages error:', e);\n }\n };\n\n const setInitialValues = () => {\n // console.devLog.info('Props client data:', props.clientData.data[0]);\n if (props.clientData.data[0].messageEventDisplayPrefs) {\n const initialValues = {};\n initialValues.chatMedium = props.clientData.data[0].chatMedium;\n initialValues.phoneNumber = props.clientData.data[0].phoneNumber;\n return initialValues;\n }\n return {\n chatMedium: 1,\n phoneNumber: props.clientData.data[0].phoneNumber,\n };\n };\n // console.devLog.info('ALL PROPS:', props.clientData.data[0]);\n if (props.clientData.data.length <= 0) {\n return null;\n }\n\n return (\n
(\n \n {/* Submits form on change: https://github.com/jaredpalmer/formik/issues/1218 */}\n \n
\n )}\n />\n );\n};\nMessagesEventPrefs.propTypes = {\n clientData: PropTypes.object.isRequired,\n};\n\nexport default MessagesEventPrefs;\n","import { invert } from 'lodash';\n/* \n Helper code to organize task types across the project\n*/\n\n// Any additions or changes here also need to be changed in the server side task types enum\nconst messageTemplateTypes = {\n accountability: 1,\n admin: 2,\n churn: 3,\n disengaged: 4,\n generalHabits: 5,\n mindset: 6,\n missedWorkout: 7,\n nutrition: 8,\n onboarding: 9,\n rocking: 10,\n workout: 11,\n resources: 12,\n};\n\n// const messageTemplateTypesArray = [\n// messageTemplateTypes.disengaged,\n// messageTemplateTypes.missedWorkout,\n// ];\nconst messageTemplateTypesArray = Object.values(messageTemplateTypes);\n\nconst stringForMessageTemplateTypes = invert(messageTemplateTypes);\n\nexport {\n messageTemplateTypes,\n stringForMessageTemplateTypes,\n messageTemplateTypesArray,\n};\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { compose, wrapDisplayName } from 'recompose';\nimport { withFetch, withLoading } from 'modules/with-fetch';\nimport { startCase, orderBy } from 'lodash';\nimport moment from 'moment';\n\nimport { withRouter } from 'react-router-dom';\n\nimport { Popover, PopoverHeader, PopoverBody } from 'reactstrap';\n\nimport { populateMergeTags, smartFilter } from 'modules/functions';\nimport {\n messageTemplateTypesArray,\n stringForMessageTemplateTypes,\n} from 'modules/enums/message-template-types';\n\nclass MessageTemplatePicker extends React.Component {\n static propTypes = {\n templatePickerOpen: PropTypes.bool,\n messageTemplatesList: PropTypes.object.isRequired,\n setFieldValue: PropTypes.func.isRequired,\n myClient: PropTypes.object.isRequired,\n myTrainer: PropTypes.object.isRequired,\n fullChatRoom: PropTypes.object.isRequired,\n match: PropTypes.object.isRequired,\n trainerId: PropTypes.any.isRequired,\n };\n\n static defaultProps = {\n templatePickerOpen: false,\n };\n\n state = {\n popoverOpen: this.props.templatePickerOpen,\n selectedType: null,\n filterOwnership: -1,\n filterString: '',\n };\n\n onFilterChange = (e) => {\n this.setState({\n filterString: e.target.value,\n });\n };\n\n toggle = () => {\n this.setState((prevState) => ({\n popoverOpen: !prevState.popoverOpen,\n }));\n };\n\n handleTemplateSelect = (selectedTemplate) => {\n const messageBody = populateMergeTags(\n selectedTemplate.message,\n this.props.myClient.data[0],\n this.props.myTrainer.data[0],\n );\n this.props.setFieldValue('templateId', selectedTemplate.id);\n this.props.setFieldValue(\n 'globalOriginTemplateId',\n selectedTemplate.globalOriginTemplateId,\n );\n this.props.setFieldValue('text', messageBody);\n\n this.setState({\n popoverOpen: false,\n });\n };\n\n changeType = (e) => {\n const newType = e.target.value;\n this.setState({\n selectedType: newType,\n });\n };\n\n handleOwnershipFilterChange = (event) => {\n const { value } = event.target;\n this.setState({ filterOwnership: Number.parseInt(value, 10) });\n };\n\n renderTypeFilter = () => (\n \n \n \n
\n );\n\n renderPhraseFilter = () => (\n \n \n \n
\n );\n\n renderOwnershipFilter = () => (\n \n
\n
\n
\n );\n\n renderMessageTemplateOptions = () => {\n const filteredTemplates = this.props.messageTemplatesList.data;\n\n let withLastSent = filteredTemplates;\n if (\n this.props.fullChatRoom &&\n this.props.fullChatRoom.data &&\n this.props.fullChatRoom.data.length > 0\n ) {\n const { messages } = this.props.fullChatRoom.data[0];\n withLastSent = filteredTemplates.map((template) => {\n const newTemplate = Object.assign({}, template);\n newTemplate.lastTemplateSend = null;\n messages.forEach((message) => {\n if (!message.templateId) {\n return;\n }\n // Right here - we are going through all the templates. We need to check against both this template IDs OR if this message's template ID matches any global origin ID\n if (\n message.templateId === template.id ||\n (template.globalOriginId &&\n message.globalOriginTemplateId === template.globalOriginId)\n ) {\n newTemplate.lastTemplateSend = message.sent;\n }\n });\n\n return newTemplate;\n });\n\n if (this.state.selectedType && this.state.selectedType !== -1) {\n withLastSent = withLastSent.filter(\n (template) => template.category == this.state.selectedType,\n );\n }\n\n if (this.state.filterOwnership !== -1) {\n withLastSent = withLastSent.filter(\n (template) => template.trainerId == this.state.filterOwnership,\n );\n }\n\n if (this.state.filterString) {\n withLastSent = withLastSent.filter(\n (template) =>\n template.title &&\n smartFilter(template.title, this.state.filterString),\n );\n }\n }\n\n withLastSent = orderBy(\n withLastSent,\n ['lastTemplateSend', 'title'],\n ['desc', 'asc'],\n );\n // Label/disable templates that have been used in the past three months\n\n return withLastSent.map((messageTemplate) => {\n let label = messageTemplate.title;\n let disabled = false;\n label = messageTemplate.trainerId === 0 ? `[G] ${label}` : label;\n if (moment(messageTemplate.lastTemplateSend).isValid()) {\n const lastSentMoment = moment(messageTemplate.lastTemplateSend).format(\n 'ddd, MMM Do',\n );\n\n // Label last sent\n label = `(Sent: ${lastSentMoment}) ${messageTemplate.title} `;\n label = messageTemplate.trainerId === 0 ? `[G] ${label}` : label;\n disabled = moment(messageTemplate.lastTemplateSend).isAfter(\n moment().subtract(3, 'months'),\n );\n }\n return (\n \n );\n });\n };\n\n render() {\n // console.devLog.info('chat text box props match:', this.props);\n // console.devLog.info('Message template picker state:', this.state);\n const positionClass =\n this.props.match.path === '/clients'\n ? 'template-popover-left'\n : 'template-popover-right';\n return (\n \n \n \n \n \n Select Message Template
\n \n {this.renderOwnershipFilter()}\n {this.renderTypeFilter()}\n {this.renderPhraseFilter()}\n {this.renderMessageTemplateOptions()}\n \n \n \n );\n }\n}\n\n/* eslint-disable react/no-multi-comp */\nconst EnhancedComponent = class PropHOCWrapper extends React.Component {\n static propTypes = {\n trainerId: PropTypes.any.isRequired,\n };\n\n constructor(props) {\n super(props);\n this.createWrappedComp();\n }\n\n // componentDidUpdate(prevProps) {\n // if (prevProps.clientId !== this.props.clientId) {\n // this.createWrappedComp();\n // }\n // }\n\n createWrappedComp = () => {\n this.Comp = compose(\n withRouter,\n withFetch(\n 'Trainer/MyMessageTemplates',\n { query: { trainerId: this.props.trainerId } },\n 'messageTemplatesList',\n ),\n withLoading('messageTemplatesList'),\n )(MessageTemplatePicker);\n };\n\n render() {\n return ;\n }\n};\n\nEnhancedComponent.displayName = wrapDisplayName(\n MessageTemplatePicker,\n 'withFetch',\n);\n\nexport default EnhancedComponent;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport $ from 'jquery';\nimport { IoIosPhonePortrait } from 'react-icons/io';\nimport Toast from 'modules/toast';\n\nimport RecordRTC from 'recordrtc';\nimport moment from 'moment';\n\nimport { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';\n\nconst captureUserMedia = (callback) => {\n const params = { audio: true, video: true };\n navigator.mediaDevices\n .getUserMedia(params)\n .then((mediaStream) => {\n callback(mediaStream);\n // console.log('Media stream?', mediaStream);\n })\n .catch((error) => {\n console.error(JSON.stringify(error));\n Toast.error('Error with video stream - possibly missing audio.');\n });\n};\n\nclass MessagesVideoModal extends React.Component {\n static propTypes = {\n videoModalToggle: PropTypes.func.isRequired,\n isVideoModalOpen: PropTypes.bool.isRequired,\n myClient: PropTypes.object.isRequired,\n modalClassNames: PropTypes.string,\n refreshEverything: PropTypes.func.isRequired,\n title: PropTypes.string,\n fileUpload: PropTypes.func.isRequired,\n };\n\n static defaultProps = {\n title: 'Record Video For Client',\n modalClassNames: '',\n };\n\n constructor(props) {\n super(props);\n this.videoRecorderObj = null;\n this.videoPlayerRef = React.createRef();\n\n this.state = {\n isRecording: false,\n isRecordingPaused: false,\n isVideoRecorderObjCreated: false,\n playbackVideoSrc: null,\n recordedVideoParams: null,\n maxVideoTime: 900, // 900 seconds (15 mins) minutes max\n timerTime: 900,\n };\n }\n\n componentDidMount() {\n this.previewRecording();\n }\n\n componentWillUnmount() {\n this.closePreviewRecorder();\n }\n\n startTimer = () => {\n // timer logic\n this.timer = setInterval(() => {\n const newTime = this.state.timerTime - 1;\n if (newTime >= 0) {\n this.setState({\n timerTime: newTime,\n });\n } else {\n clearInterval(this.timer);\n this.stopRecord();\n }\n }, 1000);\n };\n\n stopTimer = () => {\n // clear timer interval & reset timer\n clearInterval(this.timer);\n };\n\n resetTimer = () => {\n // reset timer to starting time\n this.setState({ timerTime: this.state.maxVideoTime });\n };\n\n displayTimer = () => {\n const secondsRemaining = `0${\n Math.floor(this.state.timerTime % 60) % 60\n }`.slice(-2);\n const minutesRemaining = `0${Math.floor(\n (this.state.timerTime / 60) % 60,\n )}`.slice(-2);\n\n const timeElapsed = this.state.maxVideoTime - this.state.timerTime;\n const secondsElapsed = `0${Math.floor(timeElapsed % 60) % 60}`.slice(-2);\n const minutesElapsed = `0${Math.floor((timeElapsed / 60) % 60)}`.slice(-2);\n\n return `Time elapsed: ${minutesElapsed}:${secondsElapsed}\\n\n Time left for video: ${minutesRemaining}:${secondsRemaining}`;\n };\n\n startRecord = () => {\n console.devLog.info('Starting recording');\n\n captureUserMedia((stream) => {\n this.videoPlayerRef.current.srcObject = stream;\n this.videoPlayerRef.current.muted = true;\n this.videoPlayerRef.current.volume = 0;\n this.videoRecorderObj = RecordRTC(stream, {\n type: 'video',\n canvas: {\n width: 640,\n height: 480,\n },\n mimeType: 'video/webm;codecs=h264',\n bitrate: 500000,\n videoBitsPerSecond: 500000,\n frameInterval: 15,\n });\n\n this.videoRecorderObj.startRecording();\n });\n\n this.startTimer();\n this.setState({\n isRecording: true,\n isVideoRecorderObjCreated: true,\n playbackVideoSrc: null,\n });\n };\n\n pauseRecording = () => {\n if (!this.videoRecorderObj) {\n throw new Error('No recorder object active');\n }\n this.stopTimer();\n this.videoRecorderObj.pauseRecording();\n this.setState({ isRecordingPaused: true });\n console.devLog.info('Paused recording.');\n };\n\n resumeRecording = () => {\n if (!this.videoRecorderObj) {\n throw new Error('No recorder object active');\n }\n this.startTimer();\n this.videoRecorderObj.resumeRecording();\n this.setState({ isRecordingPaused: false });\n console.devLog.info('Resumed recording.');\n };\n\n previewRecording = () => {\n captureUserMedia((stream) => {\n this.videoPlayerRef.current.srcObject = stream;\n this.videoPlayerRef.current.muted = true;\n this.videoPlayerRef.current.volume = 0;\n this.videoRecorderObj = RecordRTC(stream, {\n type: 'video',\n canvas: {\n width: 640,\n height: 480,\n },\n mimeType: 'video/webm;codecs=h264',\n bitrate: 500000,\n videoBitsPerSecond: 500000,\n frameInterval: 15,\n });\n\n this.videoRecorderObj.startRecording();\n });\n this.setState({\n isVideoRecorderObjCreated: true,\n playbackVideoSrc: null,\n });\n };\n\n stopRecord = () => {\n this.stopTimer();\n this.resetTimer();\n this.videoRecorderObj.stopRecording(() => {\n const params = {\n type: 'video/webm',\n data: this.videoRecorderObj.blob,\n id: `nf-twa-${moment().format('ddd-MMM-Do-h:mm-a')}.webm`,\n };\n this.videoPlayerRef.current.muted = false;\n this.videoPlayerRef.current.volume = 1;\n this.videoPlayerRef.current.srcObject.getTracks().forEach((track) => {\n track.stop();\n });\n // this.videoPlayerRef.current.src = this.videoRecorderObj.toURL();\n this.setState({\n playbackVideoSrc: this.videoRecorderObj.toURL(),\n recordedVideoParams: params,\n });\n console.devLog.info('recording stopped');\n this.videoRecorderObj.destroy();\n this.setState({ isRecording: false, isVideoRecorderObjCreated: false });\n });\n };\n\n closePreviewRecorder = () => {\n if (!this.state.playbackVideoSrc) {\n this.videoPlayerRef.current.srcObject.getTracks().forEach((track) => {\n track.stop();\n });\n }\n };\n\n // save to disk functionality\n saveVideo = () => {\n const link = document.createElement('a');\n document.body.appendChild(link);\n link.style = 'display: none';\n\n const url = this.state.playbackVideoSrc;\n link.href = url;\n link.download = `nf-twa-${moment().format('ddd-MMM-Do-h:mm:ss-a')}.webm`;\n link.click();\n URL.revokeObjectURL(url);\n };\n\n sendVideo = async () => {\n const params = this.state.recordedVideoParams;\n const file = new File([params.data], params.id, {\n type: 'video/webm;codecs=h264',\n });\n this.saveVideo(); // save the video first just to make sure trainer has it\n this.hideModal();\n await this.props.fileUpload(file);\n this.props.refreshEverything();\n };\n\n hideModal = () => {\n $('#video-modal').modal('hide');\n this.props.videoModalToggle();\n };\n\n render() {\n return (\n \n {this.props.title}\n \n \n {!this.state.playbackVideoSrc && (\n // No need for captions on this media\n // eslint-disable-next-line\n \n )}\n {this.state.playbackVideoSrc && (\n // No need for captions on this media\n // eslint-disable-next-line\n \n )}\n
\n \n {this.displayTimer()}\n
\n \n \n \n {this.props.myClient.data[0].chatMedium === 2 && (\n
\n \n \n SMS\n \n
\n )}\n {!this.state.isRecording && (\n
\n )}\n {this.state.isRecording && (\n
\n )}\n {this.state.isVideoRecorderObjCreated && (\n <>\n {this.state.isRecordingPaused ? (\n
\n ) : (\n
\n )}\n >\n )}\n
\n \n {this.state.playbackVideoSrc && (\n <>\n \n >\n )}\n \n
\n \n \n );\n }\n}\n\nexport default MessagesVideoModal;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { IoMdVideocam } from 'react-icons/io';\nimport MessagesVideoModal from './MessagesVideoModal';\n\nexport default class MessagesVideoControl extends React.Component {\n static propTypes = {\n fileUpload: PropTypes.func.isRequired,\n refreshEverything: PropTypes.func.isRequired,\n myClient: PropTypes.object.isRequired,\n };\n\n state = {\n showVideoModal: false,\n };\n\n videoModalToggle = () => {\n this.setState((prevState) => ({\n showVideoModal: !prevState.showVideoModal,\n }));\n };\n\n // showVideoModal = () => this.setState({ showVideoModal: true });\n // hideVideoModal = () => this.setState({ showVideoModal: false });\n\n render() {\n return (\n <>\n \n {this.state.showVideoModal ? (\n \n ) : null}\n >\n );\n }\n}\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport apiFetch from 'modules/api-fetch';\nimport { Form, Field } from 'formik';\nimport ReactPlayer from 'react-player';\nimport ReactHtmlParser from 'react-html-parser';\nimport { Progress } from 'reactstrap';\nimport { withRouter } from 'react-router-dom';\nimport Toast from 'modules/toast';\nimport Loading from 'modules/loading';\nimport { find, maxBy } from 'lodash';\nimport Textarea from 'modules/components/web/textarea';\nimport Config, { genderTypeMap } from 'modules/config';\nimport moment from 'moment';\nimport Picker from 'react-giphy-picker';\nimport Swal from 'sweetalert2';\nimport IMAGES from 'modules/assets/web';\nimport { uploadFile } from 'modules/file-handling';\nimport Dropzone from 'modules/components/web/dropzone';\nimport ClientStatusLegend from 'modules/components/web/chat/src/ClientStatusLegend';\n\nimport MessagesClientPush from './TrainerFacing/MessagesClientPush';\nimport MessagesEventPrefs from './TrainerFacing/MessagesEventPrefs';\nimport MessageTemplatePicker from './TrainerFacing/MessageTemplatePicker';\nimport MessagesVideoControl from './TrainerFacing/MessagesVideoControl';\n\n// https://www.npmjs.com/package/emoji-picker-react\n\nclass ChatTextBox extends React.Component {\n static propTypes = {\n handleSubmit: PropTypes.func.isRequired,\n values: PropTypes.object.isRequired,\n refreshEverything: PropTypes.func.isRequired,\n // nextClientId: PropTypes.any.isRequired,\n handleChange: PropTypes.func.isRequired,\n clientData: PropTypes.object.isRequired,\n isSubmitting: PropTypes.bool.isRequired,\n setFieldValue: PropTypes.func.isRequired,\n trainerId: PropTypes.number.isRequired,\n setSubmitting: PropTypes.func.isRequired,\n focused: PropTypes.bool,\n resetImageForm: PropTypes.bool.isRequired,\n className: PropTypes.string.isRequired,\n scrollToBottom: PropTypes.func.isRequired,\n myClient: PropTypes.object.isRequired,\n myTrainer: PropTypes.object.isRequired,\n fullChatRoom: PropTypes.object.isRequired,\n clientChatRoomId: PropTypes.number.isRequired,\n };\n\n static defaultProps = {\n focused: false,\n };\n\n state = {\n // showEmojiPicker: false,\n showImagePicker: false,\n showGifPicker: false,\n loadedMedia: null,\n loadedFileName: '',\n loadedFileSize: '',\n fileUploadProgress: '',\n videoUploaded: false,\n imageUploaded: false,\n isMediaLoading: false,\n };\n\n componentDidUpdate() {\n if (this.props.resetImageForm) {\n this.resetImagePicker();\n }\n }\n\n onTextareaEnterPress = (e) => {\n if (e.keyCode === 13 && (e.ctrlKey === true || e.metaKey === true)) {\n e.preventDefault();\n this.props.handleSubmit();\n }\n return null;\n };\n\n setImagePreview = (file) => {\n // console.devLog.info('File:', file);\n const reader = new FileReader();\n reader.onload = (readerFile) => {\n let videoUrl = readerFile.target.result;\n if (file.type.includes('quicktime')) {\n videoUrl = URL.createObjectURL(file);\n }\n this.setState({\n loadedMedia: videoUrl,\n loadedFileName: file.name,\n loadedFileSize: parseInt(file.size / 1000 / 1000, 10),\n });\n };\n reader.readAsDataURL(file);\n };\n\n setFileUploadProgress = (value) => {\n this.setState({\n fileUploadProgress: value,\n });\n };\n\n newUploadFile = async (file) => {\n this.setState({ isMediaLoading: true });\n if (file.size / 1000 / 1000 > 500) {\n Toast.error('That file is over 500mb, host it on YouTube or something!');\n this.resetImagePicker();\n return;\n }\n\n if (\n this.props.myClient.data[0].chatMedium === 2 &&\n file.size / 1000 / 1000 > 4\n ) {\n Toast.error('Files sent via SMS must be under 4mb.', {\n duration: 10000,\n });\n this.resetImagePicker();\n return;\n }\n\n this.setImagePreview(file);\n // console.devLog.info('File??', file);\n try {\n if (file.type.includes('video/webm')) {\n // fetch needs to use FormData for file\n const data = new FormData();\n data.append('file', file);\n const config = {\n url: 'Trainer/CreateRecordedChatVideo',\n query: {\n chatRoomId: this.props.clientChatRoomId,\n },\n method: 'POST',\n isFormData: true,\n body: data,\n timeoutMS: 90000,\n };\n await apiFetch(config);\n\n // The server actually 'sends' the message at this point\n Toast.success('Video Sent!');\n this.setState({\n isMediaLoading: false,\n loadedFileName: '',\n loadedFileSize: '',\n });\n // Refresh data??\n this.props.refreshEverything();\n } else {\n try {\n const fileGuid = await uploadFile(\n file,\n undefined,\n this.setFileUploadProgress,\n 'ChatPhotoBucket',\n );\n\n if (file.type.includes('video')) {\n this.props.setFieldValue('videoUrl', fileGuid);\n // this.props.values.videoUrl = fileGuid;\n this.setState({\n videoUploaded: true,\n });\n } else if (file.type.includes('image')) {\n this.props.setFieldValue('imageUrl', fileGuid);\n // this.props.values.imageUrl = fileGuid;\n this.setState({\n imageUploaded: true,\n });\n } else {\n Toast.error('Error: Does not match any allowed format.');\n throw new Error('Does not match allowed formats');\n }\n\n Toast.success('Media loaded into message.');\n } catch (e) {\n console.log('Erro uploading media');\n }\n }\n } catch (err) {\n Toast.error('Error uploading media');\n console.devLog.info('Error uploading media:', err);\n this.setState({\n isMediaLoading: false,\n });\n }\n this.setState({\n isMediaLoading: false,\n });\n this.props.refreshEverything();\n this.props.setSubmitting(false);\n };\n\n imagePreview = () => (\n
\n );\n\n filePreviewName = () => File Name: {this.state.loadedFileName}
;\n\n filePreviewSize = () => File Size: {this.state.loadedFileSize} Mb
;\n\n fileProgress = () => (\n \n );\n\n videoPreview = () => (\n \n \n
\n );\n\n filePicked = (file) => {\n // console.devLog.info('File:', e);\n // const file = e.target.files[0];\n this.newUploadFile(file);\n };\n\n fileUploader = () => (\n \n );\n\n gifPicker = () => (\n {\n const gifUrl = gif.downsized.url;\n\n this.props.setFieldValue('imageUrl', gifUrl);\n // this.props.values.imageUrl = gifUrl;\n this.setState({\n loadedMedia: gifUrl,\n loadedFileName: gifUrl,\n imageUploaded: true,\n loadedFileSize: parseInt(gif.downsized.size / 1000 / 1000, 10),\n showGifPicker: false,\n });\n }}\n />\n );\n\n pushWarning = () => {\n // Notify the trainer if the client will not get a push notification for this\n const clientData = this.props.myClient.data[0];\n\n if (!Config.IS_TRAINER_APP) {\n return null;\n }\n\n let pushStatus = null;\n let message = '';\n try {\n pushStatus = JSON.parse(clientData.pushStatus);\n } catch (e) {\n console.devLog.info('Error parsing push status: ', pushStatus);\n }\n if (clientData.chatMedium === 1 && clientData.chatNotifications === 0) {\n message =\n \"This client has disabled chat message notifications with their in-app chat notifications preference. See: Push Notification Permissions\";\n }\n\n if (\n clientData.chatMedium === 1 &&\n pushStatus &&\n pushStatus.notificationsEnabled === false\n ) {\n message =\n \"This client's app does not allow push notifications. See: Push Notification Permissions\";\n }\n\n if (!message) {\n return null;\n }\n\n return (\n \n
\n {ReactHtmlParser(message)}\n
\n
\n );\n };\n\n imageUploader = () => (\n \n \n \n
\n );\n\n resetImagePicker = () => {\n this.setState(\n {\n showImagePicker: false,\n loadedMedia: null,\n loadedFileName: '',\n loadedFileSize: '',\n fileUploadProgress: 0,\n videoUploaded: false,\n imageUploaded: false,\n isMediaLoading: false,\n },\n this.props.setImageReset, // eslint-disable-line\n );\n };\n\n // selectNextClient = () => {\n // this.props.history.push({\n // pathname: `/messages/${this.props.nextClientId}`,\n // // state: { orderBy },\n // });\n // };\n\n checkTime = async () => {\n // Check if the clients time is between 10pm and 6am and ask if they want to send a notification or not\n if (Config.IS_TRAINER_APP && this.props.myClient.data[0].chatMedium === 1) {\n const clientOffset = this.props.myClient.data[0].utcOffset;\n // const clientOffset = 600;\n const clientTime = moment().utcOffset(clientOffset).format('h:mm a');\n\n // Outputs in military hours so we can tell if it's too early or too late regardless of day\n const clientHour = moment().utcOffset(clientOffset).format('HH');\n\n const isTimeLate = clientHour <= 6 || clientHour >= 22;\n\n if (isTimeLate) {\n const { dismiss } = await Swal.fire({\n showCancelButton: true,\n confirmButtonText: 'Notify',\n type: 'warning',\n cancelButtonText: 'No Notification',\n showCloseButton: true,\n text: `The client's current time is: ${clientTime}, do you want to send a notification?`,\n });\n\n if (dismiss === 'cancel') {\n this.props.setFieldValue('sendNotification', false);\n }\n }\n }\n\n this.props.handleSubmit();\n };\n\n render() {\n const { className } = this.props;\n const clientData = this.props.myClient.data[0];\n const gender = find(\n genderTypeMap,\n (item) => item.value == clientData.gender,\n );\n\n let genderInitial = 'N/A';\n\n if (gender) {\n genderInitial = gender.label.substring(0, 1);\n }\n const justifyBox = Config.IS_TRAINER_APP\n ? 'justify-content-between'\n : 'justify-content-end';\n\n // console.log('Client data??', clientData);\n const lastCall = maxBy(clientData.callLogs, 'time');\n let lastCallTime = null;\n if (lastCall) {\n lastCallTime = lastCall.time;\n }\n return (\n \n );\n }\n}\n\nexport default withRouter(ChatTextBox);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Formik } from 'formik';\nimport { compose, wrapDisplayName } from 'recompose';\nimport Moment from 'moment';\nimport markMessagesAsSeen from 'modules/components/web/chat/src/functions/markMessagesAsSeen';\n\nimport apiFetch from 'modules/api-fetch';\nimport Toast from 'modules/toast';\nimport { withFetch, withLoading } from 'modules/with-fetch';\n\nimport ChatHeaderClient from './ChatHeaderClient';\nimport ChatMessagesClient from './ChatMessagesClient';\nimport ChatTextBox from '../ChatTextBox';\n\n// TODO - refactor this from including the trainer specific portions since it is only client facing now\nclass Chat extends React.Component {\n static propTypes = {\n myChatRooms: PropTypes.object.isRequired,\n myTrainer: PropTypes.object.isRequired,\n myClient: PropTypes.object.isRequired,\n };\n\n state = {\n resetImageForm: false,\n pendingOutgoingMessages: [],\n };\n\n componentDidMount() {\n this.interval = setInterval(() => {\n this.shortPoll();\n }, 15000);\n\n try {\n markMessagesAsSeen(\n this.props.myChatRooms.data[0].messages,\n this.props.myClient.aspNetUserId,\n );\n } catch (e) {\n console.devLog.info('Error marking message as seen:', e);\n }\n }\n\n componentDidUpdate(prevProps) {\n if (\n this.props.myChatRooms.data[0].messages.length !==\n prevProps.myChatRooms.data[0].messages.length\n ) {\n try {\n markMessagesAsSeen(\n this.props.myChatRooms.data[0].messages,\n this.props.myClient.aspNetUserId,\n );\n } catch (e) {\n console.devLog.info('Error marking message as soon');\n }\n }\n }\n\n componentWillUnmount() {\n clearInterval(this.interval);\n }\n\n setImageReset = () => this.setState({ resetImageForm: false });\n\n setupChatMessageForServer = (values) => ({\n ChatRoomId: this.getChatRoomForUser().id,\n body: values.text,\n imageUrl: values.imageUrl,\n videoUrl: values.videoUrl,\n });\n\n getUserData = (self = true) =>\n self ? this.props.myClient.data[0] : this.props.myTrainer.data[0];\n\n getChatRoomForUser = () => {\n const thisClientChatRoom = this.props.myChatRooms.data.filter(\n (chatRoom) => {\n const isTrainerInRoom = chatRoom.users.some(\n (user) => user.aspNetUserId === this.getUserData(false).aspNetUserId,\n );\n return isTrainerInRoom;\n },\n );\n return thisClientChatRoom[0];\n };\n\n shortPoll = () => {\n this.props.myChatRooms.refreshData(false);\n };\n\n scrollToBottom = () => {\n if (this.messagesEnd) {\n this.messagesEnd.scrollIntoView({ behavior: 'instant' });\n }\n };\n\n submitForm = async (values, formikProps) => {\n const chatMessageForServer = this.setupChatMessageForServer(values);\n\n let indexToRemove;\n // Add our outgoing messages to a temporary array that gets shown so the user gets feedback right away\n this.setState((prevState) => {\n indexToRemove = prevState.pendingOutgoingMessages.length;\n\n const pendingMessage = {\n ...chatMessageForServer,\n chatRoomId: chatMessageForServer.ChatRoomId, // It's a little pathetic we need to do this, and spend time troubleshooting it\n id: -1 - indexToRemove,\n sent: Moment().toISOString(),\n aspNetUserId: this.getUserData().aspNetUserId,\n };\n\n return {\n pendingOutgoingMessages: prevState.pendingOutgoingMessages.concat(\n pendingMessage,\n ),\n };\n });\n\n const config = {\n url: 'Client/CreateChatMessage',\n query: { isCMA: false },\n method: 'POST',\n body: chatMessageForServer,\n };\n\n formikProps.resetForm({ values: null });\n\n try {\n await apiFetch(config);\n await this.props.myChatRooms.refreshData(false);\n } catch (err) {\n Toast.error(`Error submitting message: ${err}`);\n } finally {\n // Since we waited until we both sent and refresh data, go ahead and remove the temporary messages\n // as the server will now have returned the complete new chat\n // Note this may be a possible race condition, as technically setState isn't guaranteed\n // to actually execute before the API returns, though we always expect it to\n this.setState((prevState) => ({\n pendingOutgoingMessages: prevState.pendingOutgoingMessages.filter(\n // eslint-disable-next-line no-underscore-dangle\n (message, index) => index !== indexToRemove,\n ),\n }));\n\n formikProps.setSubmitting(false);\n this.setState({\n resetImageForm: true,\n });\n }\n };\n\n render() {\n if (\n this.props.myChatRooms.data.length <= 0 ||\n this.props.myClient.data.length <= 0 ||\n this.props.myTrainer.data.length <= 0\n ) {\n return null;\n }\n\n const userRoom = this.getChatRoomForUser();\n if (!userRoom) {\n return No chatroom found!
;\n }\n\n const { messages } = userRoom;\n const messagesWithPending = messages.concat(\n this.state.pendingOutgoingMessages,\n );\n\n const trainerData = this.props.myTrainer.data[0];\n const clientData = this.props.myClient.data[0];\n\n return (\n \n
\n
\n
\n \n
\n
(\n \n )}\n />\n \n
\n );\n }\n}\n\nconst EnhancedComponent = compose(\n withFetch('Client/MyChatRooms', undefined, 'myChatRooms'),\n withFetch('Client/MyClient', undefined, 'myClient'),\n withFetch('Client/MyTrainer', undefined, 'myTrainer'),\n withLoading('myChatRooms'),\n withLoading('myClient'),\n withLoading('myTrainer'),\n)(Chat);\n\nEnhancedComponent.displayName = wrapDisplayName(Chat, 'withFetch');\n\nexport default EnhancedComponent;\n","import React from 'react';\nimport { Redirect } from 'react-router-dom';\nimport { Formik, Form, Field } from 'formik';\n\nimport { signIn } from 'modules/auth';\nimport Config from 'modules/config';\nimport IMAGES from 'modules/assets/web';\nimport queryString from 'query-string';\n\nclass SignInPage extends React.Component {\n state = {\n redirectToReferrer: false,\n };\n\n login = async values => {\n // signIn(values).then(() => this.setState({ redirectToReferrer: true }));\n const loginAttempt = await signIn(values);\n console.devLog.info('Loginattempt:', loginAttempt);\n return loginAttempt ? this.setState({ redirectToReferrer: true }) : null;\n };\n\n render() {\n const { from } = this.props.location.state || { from: { pathname: '/' } };\n const { redirectToReferrer } = this.state;\n const { email, setPassword, resetUrl } = queryString.parse(\n this.props.location.search,\n {\n ignoreQueryPrefix: true,\n },\n );\n\n if (redirectToReferrer) {\n return ;\n }\n const passwordResetLink =\n Config.ENV === 'production'\n ? 'https://www.nerdfitness.com/lostpassword/?coaching=true'\n : 'https://www.nerdfitness.com/lostpassword/?coaching=true';\n return (\n \n
(\n \n )}\n />\n \n );\n }\n}\n\nexport default SignInPage;\n\n// https://getbootstrap.com/docs/4.0/examples/sign-in/\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Redirect, withRouter, Link } from 'react-router-dom';\nimport { Formik, Form, Field, ErrorMessage } from 'formik';\nimport { Helmet } from 'react-helmet';\nimport IMAGES from 'modules/assets/web';\nimport Toast from 'modules/toast';\nimport queryString from 'query-string';\nimport apiFetch from 'modules/api-fetch';\nimport * as Yup from 'yup';\n\nclass SetPasswordPage extends React.Component {\n static propTypes = {\n location: PropTypes.object.isRequired,\n };\n\n state = {\n redirect: false,\n showError: false,\n };\n\n componentDidMount() {\n window.location.href = 'https://nerdfitness.com/lostpassword?coaching=true';\n }\n\n setPassword = async values => {\n const { email, code } = queryString.parse(this.props.location.search, {\n ignoreQueryPrefix: true,\n });\n\n const config = {\n url: 'Infusionsoft/ResetPassword',\n method: 'POST',\n query: {\n email,\n password: values.password,\n code,\n },\n body: {},\n };\n\n try {\n const response = await apiFetch(config);\n\n if (response.includes('success')) {\n Toast.success('Password set! Redirecting to sign in...');\n setTimeout(() => {\n this.setState({\n redirect: true,\n });\n }, 1000);\n } else {\n Toast.error(\n 'Encountered an error. E-mail not found, invalid token, or token already used.',\n );\n this.setState({\n showError: true,\n });\n }\n } catch (er) {\n Toast.error(\n 'Encountered an error. E-mail not found, invalid token, or token already used.',\n );\n this.setState({\n showError: true,\n });\n console.devLog.info('Error:', er);\n }\n };\n\n validationSchema = () =>\n Yup.object({\n password: Yup.string()\n .min(6, 'Minimum password length is 6 characters')\n .max(24, 'Maximum password length is 24 characters')\n .required('Password is required'),\n passwordConfirm: Yup.string()\n .oneOf([Yup.ref('password'), null], 'Passwords must match')\n .required('Password confirm is required'),\n });\n\n render() {\n const { email, code } = queryString.parse(this.props.location.search, {\n ignoreQueryPrefix: true,\n });\n if (this.state.redirect) {\n return (\n \n );\n }\n\n const disabled = !code || !email;\n return (\n \n
\n Set Password\n \n\n
(\n \n )}\n />\n \n );\n }\n}\n\nexport default withRouter(SetPasswordPage);\n\n// https://getbootstrap.com/docs/4.0/examples/sign-in/\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter, Link } from 'react-router-dom';\nimport { Formik, Form, Field, ErrorMessage } from 'formik';\nimport { Helmet } from 'react-helmet';\nimport IMAGES from 'modules/assets/web';\nimport Toast from 'modules/toast';\n\nimport apiFetch from 'modules/api-fetch';\n\nclass SetPasswordPage extends React.Component {\n static propTypes = {\n location: PropTypes.object.isRequired,\n };\n\n componentDidMount() {\n window.location.href = 'https://nerdfitness.com/lostpassword?coaching=true';\n }\n\n setPassword = async (values, formikProps) => {\n console.devLog.info('Set values:', values);\n // Default generic password reset triggers\n const config = {\n url: 'MobileAccount/ResetClientPassword',\n method: 'POST',\n query: {\n clientEmail: values.email,\n },\n };\n\n try {\n const response = await apiFetch(config);\n\n if (response) {\n Toast.success('Great! Triggered a password reset e-mail to you.');\n } else {\n Toast.error(\n 'Encountered an error. E-mail not found or other issue encountered',\n );\n }\n } catch (er) {\n Toast.error(\n 'Encountered an error. E-mail not found or other issue encountered.',\n );\n console.devLog.info('Error:', er);\n } finally {\n formikProps.setSubmitting(false);\n }\n };\n\n render() {\n return (\n \n
\n Reset Password\n \n\n
(\n \n )}\n />\n \n );\n }\n}\n\nexport default withRouter(SetPasswordPage);\n\n// https://getbootstrap.com/docs/4.0/examples/sign-in/\n","import React from 'react';\nimport ReactPlayer from 'react-player';\nimport { Helmet } from 'react-helmet';\n\nexport default class QuestionnaireComplete extends React.Component {\n render() {\n return (\n \n
\n
\n Questionnaire Complete!\n \n
\n
\n
You’re all set! Click play below.
\n \n
\n
\n
\n
\n \n
\n
\n
Hey Steve, here!
\n\n
\n Did you watch the video above? If you didn’t, click play now\n (seriously, this is your first mission).\n
\n\n
\n As I said above, while we don’t have the perfect robot algorithm\n programmed to pair your coaching (BEEP BOOP), we do take time to\n ensure every client is matched by hand with the best coach\n available.\n
\n\n
\n I know you are fired up. And I know waiting, especially when you\n are fired up, is the HARDEST THING IN THE WORLD TO DO!!!!!!!!!\n
\n\n
\n That said, I do know this is worth it. We’ve seen it over and over\n for clients from all walks of life. I promise that we’ll do\n everything we can to make the wait worth it for you!\n
\n\n
\n So next step: give us a little time to line up a great Yoda for\n the journey ahead, and we’ll be in touch soon with details!\n
\n\n {/*
What I need you to do now?
\n\n
Check your email.
\n\n
\n We’ve sent you a link on how to get started while you wait, along\n with information on access to our digital Academy and basic\n movements you can start to get familiar with if you aren’t\n already.\n
\n\n
\n Already making progress? That’s great too - keep that up in while\n we do our thing to get you started. Don’t use this wait time as an\n excuse to lose any momentum.\n
\n\n
\n Once again, welcome. On behalf of all of us here on Team Nerd\n Fitness, we’re freakin’ pumped you’ve decided to join the Coaching\n ranks.\n
\n\n
\n And we’re all excited to see what you can accomplish over the next\n year or two in the program.\n
\n\n
\n Check your email now for more information. And we’ll email again\n when we are ready to get rolling.\n
*/}\n\n
Signing out!
\n\n
-Steve (Rebel One)
\n
\n
\n
\n );\n }\n}\n","const isExistingOnboardingEntry = (questionnaireData) => {\n // Handle legacy questions - allow user to view everything\n\n if (\n !questionnaireData.data ||\n questionnaireData.data.length <= 0 ||\n questionnaireData.data[0] === false ||\n questionnaireData.data[0].questionnaire === null\n ) {\n return false;\n }\n return true;\n};\n\nconst hasLegacyOnboardingData = (questionnaireData) => {\n if (\n questionnaireData.data &&\n questionnaireData.data.length > 0 &&\n questionnaireData.data[0].legacyQuestionnaire !== null\n ) {\n return true;\n }\n\n return false;\n};\n\nexport { isExistingOnboardingEntry, hasLegacyOnboardingData };\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport IMAGES from 'modules/assets/web';\nimport Toast from 'modules/toast';\nimport { Link } from 'react-router-dom';\n\nconst CompleteWaiver = props => {\n const { waiverCheck } = props;\n if (props.myWaiver.data.length <= 0) {\n Toast.error(\n 'Error loading waiver. Please refresh the page or contact customer support',\n );\n return null;\n }\n return (\n \n
\n

\n
\n
\n
\n
Waiver Time!
\n
\n Click the button below to sign your NF Coaching waiver. It will\n open up in a new tab; once your waiver is complete, return here\n to answer a questionnaire about your current lifestyle, habits,\n and goals!\n
\n\n
\n \n \n
\n {`Once you've completed and signed the waiver, return here. You will automatically continue on once it finds your signed waiver, or you can press the button below to manually check.`}\n
\n
\n {props.checkingWaiver && (\n
\n
Checking Waiver Status...\n\n
\n Due to an error in the signing service, if you have already\n completed the waiver please use this link to continue.\n \n

\n
\n )}\n
\n
\n

\n
\n
\n
\n
\n
\n );\n};\n\nCompleteWaiver.propTypes = {\n myWaiver: PropTypes.object.isRequired,\n waiverCheck: PropTypes.func.isRequired,\n isHiddenSidebar: PropTypes.bool.isRequired,\n checkingWaiver: PropTypes.bool.isRequired,\n};\n\nexport default CompleteWaiver;\n","const noteBoxTypes = {\n calendar: 1,\n foodlog: 2,\n bodyStats: 7,\n lifestyleHabits: 9,\n nutritionHabits: 10,\n workoutHabits: 11,\n programmingNotes: 12,\n rapportNotes: 13,\n callLog: 14,\n retentionNotes: 15,\n struggles: 16,\n wins: 18,\n moments: 17,\n currentInjuries: 19,\n goals: 20,\n kryptonite: 21,\n miscLinksNotes: 22,\n superpowers: 23,\n equipment: 24,\n healthNotes: 25,\n quickSummary: 26,\n family: 27,\n alternateInfo: 28,\n privateChurnNotes: 29,\n churnThreatNotes: 30,\n renewalEmail: 31,\n salesNotes: 32,\n};\n\nconst mapTypeToLabels = new Map([\n [noteBoxTypes.foodlog, { shortLabel: 'Food' }],\n [noteBoxTypes.bodyStats, { shortLabel: 'Body' }],\n [noteBoxTypes.lifestyleHabits, { shortLabel: 'Life' }],\n [noteBoxTypes.nutritionHabits, { shortLabel: 'Nutr' }],\n [noteBoxTypes.workoutHabits, { shortLabel: 'Work' }],\n [noteBoxTypes.programmingNotes, { shortLabel: 'Prog' }],\n [noteBoxTypes.rapportNotes, { shortLabel: 'Rapp' }],\n [noteBoxTypes.retentionNotes, { shortLabel: 'Ret' }],\n [noteBoxTypes.struggles, { shortLabel: 'Str' }],\n [noteBoxTypes.wins, { shortLabel: 'Win' }],\n [noteBoxTypes.moments, { shortLabel: 'Mom' }],\n [noteBoxTypes.currentInjuries, { shortLabel: 'Inj' }],\n [noteBoxTypes.kryptonite, { shortLabel: 'Krypt' }],\n [noteBoxTypes.miscLinksNotes, { shortLabel: 'Misc' }],\n [noteBoxTypes.superpowers, { shortLabel: 'Super' }],\n [noteBoxTypes.equipment, { shortLabel: 'Equip' }],\n [noteBoxTypes.healthNotes, { shortLabel: 'Health' }],\n [noteBoxTypes.quickSummary, { shortLabel: 'Quick' }],\n [noteBoxTypes.family, { shortLabel: 'Fam' }],\n [noteBoxTypes.alternateInfo, { shortLabel: 'Alt' }],\n]);\n\nexport { mapTypeToLabels, noteBoxTypes };\n","import { onboardingQuestionMap } from 'modules/config';\n\nconst onboardingFormData = {\n version: 3,\n name: 'Onboarding Form',\n groups: [\n // 1 - BASICS\n {\n name: 'basics',\n header: 'Help us learn more about who you are today!',\n caption: 'Personal Info',\n intro:\n \"Hey there, and welcome to Nerd Fitness Coaching! As we embark on this journey together, we want to learn all we can about you so we can meet you where you are to build a program tailored to your needs.
This questionnaire will take approximately 20-30 minutes to complete, and we recommend filling everything out at once. We’ll use the info you provide, along with notes from your call, to match you up with a Coach perfectly suited to be your guide on your quest to level up. The information you share will never be shared publicly without your consent, so please answer each question candidly. We have a zero judgment policy and don’t care where you came from - only where you’re going.
Let's get started...
\",\n fields: [\n {\n type: 'text',\n name: 'nickname',\n required: true,\n caption: 'What name do you prefer we call you?',\n customClassName: 'col-sm-12 col-md-6 mx-auto',\n placeholder: '',\n },\n {\n type: 'select',\n name: 'pronouns',\n required: true,\n caption: 'What pronouns do you use?',\n customClassName: 'col-sm-10 col-md-8',\n options: onboardingQuestionMap.pronouns,\n },\n {\n type: 'select',\n name: 'coachGenderPreference',\n required: false,\n caption: 'Do you prefer working with a Coach of a particular gender?',\n customClassName: 'col-sm-10 col-md-8',\n options: onboardingQuestionMap.coachGenders,\n },\n {\n type: 'date',\n name: 'birthday',\n caption: 'When is your birthday?',\n required: true,\n customClassName: 'col-12 mx-auto text-center',\n placeholder: '',\n },\n {\n type: 'fieldsArray',\n name: 'heightGroup',\n required: false,\n caption: 'How tall are you?',\n items: [\n {\n type: 'number',\n name: 'heightFeet',\n caption: '',\n placeholder: '',\n unit: 'ft',\n },\n {\n type: 'number',\n name: 'heightInches',\n caption: '',\n placeholder: '',\n unit: 'in',\n },\n ],\n },\n {\n type: 'text',\n name: 'weight',\n required: false,\n caption: 'What is your approximate weight?',\n customClassName: 'col-sm-12 col-md-6 mx-auto',\n placeholder: '',\n },\n\n {\n type: 'text',\n name: 'mailingAddress1',\n required: true,\n caption: 'Mailing Address 1',\n customClassName: 'col-sm-12 col-md-6 mx-auto',\n placeholder: '',\n },\n {\n type: 'text',\n name: 'mailingAddress2',\n caption: 'Mailing Address 2',\n customClassName: 'col-sm-12 col-md-6 mx-auto',\n placeholder: '',\n },\n {\n type: 'fieldsArray',\n required: false,\n name: 'addressGroup',\n caption: '',\n items: [\n {\n type: 'text',\n name: 'city',\n caption: 'City',\n required: true,\n customClassName: 'col-sm-10 col-md-10 mx-auto',\n placeholder: '',\n },\n {\n type: 'text',\n name: 'state',\n caption: 'State',\n required: true,\n customClassName: 'col-sm-10 col-md-10 mx-auto',\n placeholder: '',\n },\n ],\n },\n {\n type: 'fieldsArray',\n required: false,\n name: 'addressGroup2',\n caption: '',\n items: [\n {\n type: 'text',\n name: 'zip',\n required: true,\n caption: 'Zip',\n customClassName: 'col-sm-10 col-md-10 mx-auto',\n placeholder: '',\n },\n {\n type: 'text',\n name: 'country',\n caption: 'Country',\n required: true,\n customClassName: 'col-sm-10 col-md-10 mx-auto',\n placeholder: '',\n },\n ],\n },\n {\n type: 'phone',\n name: 'phoneNumber',\n required: true,\n caption: 'Phone Number',\n customClassName: 'col-sm-4 col-md-4 mx-auto text-center',\n placeholder: '',\n },\n {\n type: 'select',\n name: 'unitPreferences',\n required: true,\n caption: 'Which units of measurement would you prefer?',\n customClassName: 'col-sm-10 col-md-8',\n options: onboardingQuestionMap.unitPreferences,\n },\n ],\n },\n // 2 should be HABITS now, but renamed\n {\n name: 'Getting-to-know-you',\n header: 'Getting to Know You',\n caption: 'Getting To Know You',\n intro:\n 'We’ll focus a lot of our time together talking about fitness, nutrition, and lifestyle habit changes, but we want to build a relationship that goes a bit deeper than that. Before we get into the nitty gritty fitness details, let’s talk about the awesome human you are.
',\n fields: [\n {\n type: 'textarea',\n name: 'focusesPassions',\n caption:\n 'Fitness and nutrition aside, what are 3 of the most important focuses or passions in your life right now?',\n required: true,\n subCaption:\n 'Hobbies? Work projects? Interests? What makes you tick? ',\n },\n {\n type: 'textarea',\n name: 'importantPeoplePets',\n caption:\n 'Who are the most important people (or pets!) in your world?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'nerdyHobbies',\n caption:\n 'What other hobbies, interests, or fandoms do you enjoy, if any?',\n required: false,\n },\n {\n type: 'textarea',\n name: 'interestingFact',\n caption:\n 'What is one interesting fact about you that not many people know?',\n },\n {\n type: 'textarea',\n name: 'characterLike',\n caption:\n 'Is there a character from a movie, tv, or book that you strive to be more like?',\n },\n ],\n },\n // 3 will be 'thinking about change'\n {\n name: 'thinking-about-change',\n caption: 'Thinking About Change',\n header: 'Thinking About Change',\n intro:\n 'You took your first courageous step in this journey by deciding to join. High five! In the questions that follow, tell us a little more about your motivations for taking that step and your expectations of our time together.
',\n fields: [\n {\n type: 'textarea',\n name: 'attractedNFCoaching',\n caption: 'What attracted you to Nerd Fitness Coaching?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n placeholder: '',\n },\n {\n type: 'textarea',\n name: 'nervousAboutJourney',\n caption:\n 'What are you most nervous about on this journey, if anything?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n placeholder: '',\n },\n {\n type: 'textarea',\n name: 'excitedAbout',\n caption: 'On the flipside, what are you most excited about?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n placeholder: '',\n },\n {\n type: 'textarea',\n name: 'retentionNotes',\n caption:\n 'Why are you ready to make healthy lifestyle changes now?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'desiredCoachNumber',\n caption:\n 'What is the number one thing you want to work on with your Coach?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n // subCaption: '(Feel free to include additional goals here.)',\n },\n {\n type: 'textarea',\n name: 'coachExpectations',\n caption:\n 'What expectations, if any, do you have of your Coach during our journey together?',\n required: true,\n },\n {\n type: 'select',\n name: 'bigProjectMostHelpful',\n caption:\n 'Think about a time when you completed a big project at school/work. Which of these was MOST helpful in accomplishing the task?',\n subCaption: '(Choose the best fit, even if it isn’t perfect.)',\n required: true,\n customClassName: 'col-sm-10 col-md-8',\n align: 'center',\n view: 'block',\n options: [\n {\n caption: '...',\n value: null,\n },\n {\n caption:\n 'Having a clear direction for what this looked like when done and done well.',\n value:\n 'Having a clear direction for what this looked like when done and done well.',\n },\n {\n caption:\n 'Having a clear explanation of the justifications and reasoning behind the project.',\n value:\n 'Having a clear explanation of the justifications and reasoning behind the project.',\n },\n {\n caption:\n 'Having clear deadlines to meet, as well as other people relying on me.',\n value:\n 'Having clear deadlines to meet, as well as other people relying on me.',\n },\n {\n caption:\n 'Having the freedom to choose how I wanted to tackle the project, without any oversight.',\n value:\n 'Having the freedom to choose how I wanted to tackle the project, without any oversight.',\n },\n ],\n },\n {\n type: 'select',\n name: 'whichDescribesYouBest',\n caption: 'Which of these do you think describes you the best?',\n subCaption: '(Choose the best fit, even if it isn’t perfect.)',\n required: true,\n customClassName: 'col-sm-10 col-md-8',\n align: 'center',\n view: 'block',\n options: [\n {\n caption: '...',\n value: null,\n },\n {\n caption:\n 'I’m good at organizing my time to get both work and my personal goals accomplished.',\n value:\n 'I’m good at organizing my time to get both work and my personal goals accomplished.',\n },\n {\n caption:\n 'If I set my mind to doing something, I’ll often do it, even if it means ignoring peer pressure.',\n value:\n 'If I set my mind to doing something, I’ll often do it, even if it means ignoring peer pressure.',\n },\n {\n caption:\n 'I tend to work really hard when others need something from me, even if it means sacrificing my own time for someone else.',\n value:\n 'I tend to work really hard when others need something from me, even if it means sacrificing my own time for someone else.',\n },\n {\n caption:\n 'I put a high value on choice in all aspects of my life. I tend to do things because I want to, not because I “should”.',\n value:\n 'I put a high value on choice in all aspects of my life. I tend to do things because I want to, not because I “should”.',\n },\n ],\n },\n {\n type: 'textarea',\n name: 'succeededInChangingHabit',\n caption:\n 'Briefly tell us about a time when you succeeded in changing an important habit, even if for a short time.',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'obstacles',\n caption:\n 'What obstacles, if any, have blocked your attempts to change in the past, or do you anticipate blocking your attempt this time?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'coachSupportTough',\n caption:\n 'How can your Coach help support you best when times get tough or you encounter those obstacles?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'startingCoupleWeeksComingUp',\n caption:\n 'What’s coming up for you in the next couple of weeks as we get started on the program? What challenges, if any, could you see popping up?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'select',\n name: 'adjustmentConfidence',\n required: false,\n caption:\n 'On a scale from 1-5, how confident are you that you’ll be able to make adjustments that align with your long term goals? ',\n subCaption: '(1 = not very confident, 5 = nothing can stop me!)',\n customClassName: 'col-sm-12 col-md-4',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n ],\n },\n {\n name: 'fitness',\n caption: 'Fitness',\n header: 'Fitness',\n intro:\n 'Every person comes to Nerd Fitness Coaching with unique goals and priorities. The next three pages of questions will hone in on each of the pillars of a healthy lifestyle that we embrace at Nerd Fitness: Fitness, Nutrition, and Lifestyle/Mindset.
',\n fields: [\n {\n type: 'textarea',\n name: 'fitnessExperience',\n caption:\n 'What experience do you have with fitness or physical activity? Any programs you have followed?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'pastWorkOuts',\n caption:\n 'What are the biggest struggles you have had with exercising in the past?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'fitnessSuccessFirstMonth',\n caption:\n 'In regards to fitness, what does success look like in our first month together?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'goals',\n caption:\n 'Thinking 6-12 months down the line, what fitness goals will future you be excited to have accomplished?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'text',\n name: 'daysWorkOut',\n caption: 'How many days a week do you currently exercise?',\n required: true,\n customClassName: 'col-sm-12 col-md-4 mx-auto',\n },\n {\n type: 'text',\n name: 'daysCommitWorkout',\n caption:\n 'How many days per week, if any, would you like to exercise as we get started?',\n required: true,\n customClassName: 'col-sm-12 col-md-4 mx-auto',\n },\n {\n type: 'textarea',\n name: 'daysBestWorkOuts',\n caption:\n 'Which days are typically best for your workouts? Any days that are completely unavailable?',\n required: true,\n customClassName: 'col-sm-10 mx-auto',\n },\n {\n type: 'text',\n name: 'timePerWorkout',\n caption:\n 'How much time per workout are you ready to commit as we build your initial plan?',\n required: true,\n customClassName: 'col-sm-12 col-md-4 mx-auto',\n },\n {\n type: 'textarea',\n name: 'workoutLocation',\n caption:\n 'Do you prefer to complete most of your workouts at home, at the gym, on-the-go, a little of each, or someplace else?',\n required: true,\n customClassName: 'col-sm-10 mx-auto',\n },\n // {\n // type: 'text',\n // name: 'currentTrainingProgram',\n // caption:\n // 'Are you currently following a specific training program that you enjoy? If so, provide details.',\n // customClassName: 'col-sm-10 col-md-6 mx-auto',\n // placeholder: '',\n // },\n // {\n // type: 'select',\n // name: 'completingWorkOuts',\n // caption: 'Where do you prefer to complete most of your workouts?',\n // customClassName: 'col-sm-10 col-md-8',\n // options: [\n // {\n // caption: '...',\n // value: 'null',\n // },\n // {\n // caption: 'At a gym',\n // value: 'gym',\n // },\n // {\n // caption: 'At home',\n // value: 'home',\n // },\n // {\n // caption: 'A combination of both',\n // value: 'both',\n // },\n // ],\n // },\n\n // {\n // type: 'multiselect',\n // name: 'availableWorkOuts',\n // caption: 'What equipment do you have available for your workouts?',\n // subCaption: '(check all that apply)',\n // view: 'double',\n // labels: [\n // 'Jump rope',\n // 'Resistance Bands',\n // 'TRX',\n // 'Dumbbells',\n // 'Pull-up Bar',\n // 'Gymnastic rings',\n // 'Squat rack',\n // 'Barbell',\n // ],\n // },\n\n {\n type: 'textarea',\n name: 'additionalEquipment',\n required: true,\n caption:\n 'What equipment do you have available for your workouts? If you prefer to workout at the gym, briefly describe the type of gym you go to.',\n customClassName: 'col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'extraEquipmentLearnOrDislike',\n required: true,\n caption:\n 'Are there any pieces of equipment you enjoy using or want to learn how to use? Any pieces you dislike?',\n customClassName: 'col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'exerciseActivitiesRegular',\n required: true,\n caption:\n 'Are there any types of exercises or activities that you’re interested in making a regular part of your life? Any you dislike?',\n customClassName: 'col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'injuries',\n required: true,\n caption:\n 'Any past or present injuries or daily discomforts that would affect physical activity? What rehabilitation, if any, have you undergone?',\n customClassName: 'col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'concerns',\n required: true,\n caption:\n 'What, if any, physical concerns or apprehensions do you have with starting this program?',\n customClassName: 'col-md-10 mx-auto',\n },\n {\n type: 'select',\n name: 'scaleChangeFitnessHabits',\n caption:\n 'On a scale of 1-5, how ready are you to make changes to your fitness habits? ',\n subCaption: '(1 = not very confident, 5 = nothing can stop me!)',\n required: true,\n customClassName: 'col-sm-12 col-md-4',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n ],\n },\n {\n name: 'nutrition-info',\n caption: 'Nutrition',\n header: 'Nutrition',\n intro:\n 'They say nutrition is 80-90% of the battle as we work to hit fitness goals, and every client comes to NF Coaching with varied experience. From kitchen novices to expert chefs, counting macros to snapping photos of each meal to create awareness, there are no wrong starting points. Tell us a little more about your nutrition path!
',\n fields: [\n {\n type: 'textarea',\n name: 'biggestPastNutrition',\n required: true,\n caption:\n \"What's the biggest struggle you've had with nutrition in the past?\",\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'foundSuccess',\n caption:\n 'What, if anything, has worked for you regarding nutrition in the past?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n // subCaption: '(What kind of routines, nutrition, etc.)',\n },\n {\n type: 'textarea',\n name: 'averageDayEating',\n caption: 'What does an average day of eating look like for you now?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n // subCaption: '(What kind of routines, nutrition, etc.)',\n },\n {\n type: 'textarea',\n name: 'nutritionSuccessFirstMonth',\n caption:\n 'In regards to nutrition, what does success look like in our first month together?',\n subCaption:\n 'Do you want to learn a skill? Begin modifying a particular habit? Become more aware of what you’re eating? Hone in on a macro breakdown that suits your goals? Something else?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'nutritionFutureGoals',\n caption:\n 'Thinking 6-12 months down the line, what nutrition goals will future you be excited to have accomplished?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'nutritionChanges',\n caption:\n 'What, if any, nutrition changes are you interested in tackling or learning more about first?',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'select',\n name: 'scaleEnjoyCooking',\n caption: 'On a scale of 1-5, how much do you enjoy cooking?',\n subCaption: '(1 = I never want to cook, 5 = I love cooking!)',\n required: true,\n customClassName: 'col-sm-12 col-md-4',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n // {\n // type: 'multiselect',\n // name: 'nutritionalExperience',\n // caption: \"What's your experience with nutrition?\",\n // subCaption: '(Check all that apply)',\n // required: true,\n // align: 'center',\n // view: '',\n // labels: [\n // 'I am a complete food newbie - I eat whatever I want, whenever I want',\n // \"I'm aware that I eat some foods that are unhealthy, but I'm not sure how to change these habits.\",\n // 'I know how to read a nutritional label and generally know what macronutrients are.',\n // \"I've counted calories before and it caused me anxiety, or it is something I'm not comfortable with doing.\",\n // 'I weigh and measure everything I eat (or have in the past) and am completely comfortable with this',\n // \"I've tried diets like Paleo, Whole 30, Atkins, Keto, or IIFYM\",\n // ],\n // },\n {\n type: 'select',\n name: 'comfortableInTheKitchen',\n subCaption:\n '(1 = I have no idea what I’m doing, 5 = I’m a pretty proficient cook!)',\n caption:\n 'On a scale of 1-5, what’s your comfort level in the kitchen? ',\n required: true,\n customClassName: 'col-sm-12 col-md-6',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n {\n type: 'textarea',\n name: 'preparesMeals',\n caption: 'Who prepares most of your meals currently? ',\n subCaption: '(Ex: Me, a family member or roommate, fast food, etc)',\n required: true,\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'specificDietaryRestriction',\n caption:\n 'Do you have any specific dietary restriction or preferences?',\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'eatingStress',\n caption:\n 'Are there any times you feel more stressed about eating than normal?',\n subCaption:\n '(Ex: social situations, at work, sitting in front of the tv, etc)',\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'troubleAvoidingFoods',\n caption: 'Are there any types of foods you have trouble avoiding?',\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'textarea',\n name: 'foodEatMoreDislike',\n caption:\n 'Are there any types of foods you want to eat more of but generally dislike?',\n customClassName: 'col-sm-12 col-md-10 mx-auto',\n },\n {\n type: 'select',\n name: 'scaleReadyNutritionHabits',\n caption:\n 'On a scale of 1-5, how ready are you to make changes to your nutrition habits?',\n subCaption: '(1 = not very confident, 5 = nothing can stop me!)',\n required: true,\n customClassName: 'col-sm-12 col-md-6',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n // {\n // type: 'multiselect',\n // name: 'mostEatMeals',\n // caption: 'Where do you eat most of your meals?',\n // align: 'center',\n // view: 'double',\n // labels: [\n // 'At home or work, with food we prepare',\n // 'At home or work, with takeout',\n // 'At a restaurant or drive-thru',\n // 'From a cafeteria',\n // ],\n // },\n ],\n },\n {\n name: 'lifestyle-mindset',\n caption: 'Lifestyle/Mindset',\n header: 'Lifestyle/Mindset',\n intro:\n 'Last (but not least), let’s talk about your lifestyle and mindset. While fitness and nutrition typically make up a significant part of your programming, there are plenty of other important factors that will impact your ability to hit the goals we set together.
With your lifestyle, there may be stress, poor sleep, or less than ideal social support. With your mindset, there may be any number of tough thoughts that you deal with as we work towards change. Your Coach can also help work with you on sustainable habit changes to help in these areas too!
',\n\n fields: [\n {\n type: 'textarea',\n name: 'lifestyleMindsetHabits',\n caption:\n 'What, if any, additional lifestyle or mindset habits would you be interested in working on with your Coach?',\n subCaption:\n '(Ex: stress, sleep, meditation, social support, hobbies, decluttering, etc)',\n },\n {\n type: 'text',\n name: 'cigarettesPerWeek',\n caption: 'Do you smoke? If so, approximately how many per day?',\n customClassName: 'col-sm-10 col-md-4',\n },\n {\n type: 'text',\n name: 'drinksPerWeek',\n caption: 'Do you drink alcohol? How many drinks per week?',\n required: true,\n customClassName: 'col-sm-10 col-md-4',\n },\n {\n type: 'text',\n name: 'hoursSleepNight',\n caption: 'On average, how many hours of sleep do you get a night?',\n required: true,\n customClassName: 'col-sm-10 col-md-4',\n },\n {\n type: 'select',\n name: 'scaleSleepQuality',\n multiple: false,\n caption: 'On a scale of 1-5, how would you rate your sleep quality? ',\n subCaption:\n '(1 = I’m always dragging, 5 = I feel fantastic every morning)',\n required: true,\n customClassName: 'col-sm-12 col-md-6',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n {\n type: 'textarea',\n name: 'travel',\n caption:\n 'How often do you travel? For how long at a time? Are there any unique challenges that come with your travel schedule?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'yourHousehold',\n caption:\n 'What are your living arrangements? How do they have a positive or negative impact, if any, on your goals?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'howMuchSupport',\n caption:\n 'How much support do you have from family, friends and coworkers on your quest to get healthy?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'existingPractitionerSupport',\n caption:\n 'Are you working with any other practitioners on your health and wellness goals (doctor, therapist, nutritionist, physical therapist, etc.)?',\n required: true,\n },\n {\n type: 'select',\n name: 'currentStressLevel',\n caption:\n 'On a scale from 1-5, what is your current average level of stress?',\n subCaption: '(1 = super calm, 5 = every day is mentally exhausting)',\n required: true,\n customClassName: 'col-sm-12 col-md-6',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n {\n type: 'textarea',\n name: 'stressCatalyst',\n caption:\n 'In times of higher stress, what is typically the catalyst? What do you typically do to cope?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'averageWeekdayTime',\n caption: 'On an average weekday, how do you spend your time?',\n required: true,\n },\n {\n type: 'textarea',\n name: 'averageWeekendTime',\n caption: 'On an average weekend, how do you spend your time?',\n required: true,\n },\n {\n type: 'select',\n name: 'scaleMindsetChanges',\n caption:\n 'On a scale of 1-5, how ready are you to make changes to your mindset habits?',\n subCaption: '(1 = not very confident, 5 = nothing can stop me!)',\n required: true,\n customClassName: 'col-sm-12 col-md-6',\n options: onboardingQuestionMap.oneToFiveScale,\n },\n\n // {\n // type: 'textarea',\n // name: 'kryptonite',\n // caption: 'What is your kryptonite in your quest to get healthy?',\n // required: true,\n // subCaption:\n // 'This could be anything that constantly impedes you from achieving your goals: self-sabotaging mindset, unsupportive family, insatiable sweet tooth, inconsistent motivation, etc.',\n // },\n ],\n },\n {\n name: 'finish',\n caption: 'And Away We Go!',\n header: 'And Away We Go!',\n intro:\n 'Whew! We covered a lot of ground!! Thanks for taking the time to tell us what’s important in your own words.
',\n\n fields: [\n {\n type: 'select',\n name: 'prioritiesFirst',\n caption:\n 'Reflecting on everything we’ve just gone over regarding fitness, nutrition, and lifestyle/mindset, which category is your MOST important area of focus as we begin building your customized program together?',\n customClassName: 'col-sm-10 col-md-8',\n options: [\n {\n caption: '...',\n value: 'null',\n },\n {\n caption: 'Fitness',\n value: 'fitness',\n },\n {\n caption: 'Nutrition',\n value: 'nutrition',\n },\n {\n caption: 'Mindset',\n value: 'mindset',\n },\n ],\n },\n {\n type: 'select',\n name: 'prioritiesSecond',\n caption: 'Which category is your next priority?',\n customClassName: 'col-sm-10 col-md-8 mx-auto',\n options: [\n {\n caption: '...',\n value: 'null',\n },\n {\n caption: 'Fitness',\n value: 'fitness',\n },\n {\n caption: 'Nutrition',\n value: 'nutrition',\n },\n {\n caption: 'Mindset',\n value: 'mindset',\n },\n ],\n },\n {\n type: 'textarea',\n name: 'wantToKnowAboutCoach',\n customClassName: 'col-sm-10 col-md-8 mx-auto',\n caption:\n 'Is there anything we didn’t cover that you’d like your Coach to know?',\n },\n ],\n },\n ],\n};\n\nexport default onboardingFormData;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactHtmlParser from 'react-html-parser';\nimport Config from 'modules/config';\n\nexport default class DisplayLegacyOnboardingEntry extends React.Component {\n static propTypes = {\n legacyQuestionnaire: PropTypes.string.isRequired,\n };\n state = {\n showLegacy: false,\n };\n\n displayInfo = () => (\n \n
{ReactHtmlParser(this.props.legacyQuestionnaire)}
\n
\n );\n render() {\n // console.devLog.info('legacy props:', this.props);\n return (\n \n
\n {Config.IS_TRAINER_APP\n ? `Existing Trainerize Data Found`\n : `You've got existing data from Trainerize!`}\n
\n
\n {Config.IS_TRAINER_APP\n ? `Client has legacy trainerize data, if they did the document a button will appear below`\n : `This questionnaire is not required for you, but you can fill it in if\n you'd like to get up-to-date information into your profile here.`}\n
\n\n {this.props.legacyQuestionnaire !== '' && (\n
\n )}\n {this.state.showLegacy && this.displayInfo()}\n
\n );\n }\n}\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactHtmlParser from 'react-html-parser';\nimport classNames from 'classnames';\n\nimport IMAGES from 'modules/assets/web';\n\nconst OnboardingHeader = props => {\n const { currentGroup, groups } = props;\n\n const renderBgImage = () => {\n if (currentGroup !== 1) {\n return (\n
\n );\n }\n return null;\n };\n\n const renderPrefaceHeaderBg = () => (\n \n
\n

\n {/*

*/}\n
\n
\n );\n\n const renderMobileBg = () => (\n \n

\n

\n
\n );\n\n return (\n \n {renderBgImage()}\n
\n {currentGroup === 1 && renderPrefaceHeaderBg()}\n
{groups[currentGroup - 1].header}
\n
\n
\n {/*
\n {ReactHtmlParser(groups[currentGroup - 1].intro)}\n
*/}\n
\n
\n {renderMobileBg()}\n

\n
\n
\n );\n};\n\nOnboardingHeader.propTypes = {\n currentGroup: PropTypes.number.isRequired,\n groups: PropTypes.array.isRequired,\n};\n\nexport default OnboardingHeader;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport PhoneInput from 'react-phone-number-input';\n\nimport 'react-phone-number-input/style.css';\n\nclass PhoneNumberInput extends React.Component {\n static propTypes = {\n number: PropTypes.any,\n fieldName: PropTypes.string.isRequired,\n setFieldValue: PropTypes.func.isRequired,\n setFieldTouched: PropTypes.func.isRequired,\n disableAutosave: PropTypes.bool,\n };\n\n static defaultProps = {\n number: null,\n disableAutosave: true,\n };\n\n constructor(props) {\n super(props);\n\n const phone = this.props.number;\n\n this.state = {\n phone,\n };\n }\n\n handleChange = (phone) => {\n // This ensures that the picked date is set as noon of the client's timezone.\n\n this.setState({ phone }, () => {\n this.props.setFieldValue(this.props.fieldName, phone);\n });\n };\n\n render() {\n const inputValue = this.state.phone;\n\n return (\n <>\n \n >\n );\n }\n}\n\nexport default PhoneNumberInput;\n","import PhoneNumberInput from './src/PhoneNumberInput';\n\nexport default PhoneNumberInput;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\nimport queryString from 'query-string';\n\nconst StepControlButton = props => {\n const {\n groups,\n formikProps,\n currentGroup,\n onNextHandler,\n setSubmitPossible,\n isExistingEntry,\n } = props;\n const { isSubmitting, isValid } = formikProps;\n const { forceUpdate } = queryString.parse(props.location.search, {\n ignoreQueryPrefix: true,\n });\n const hasTrainer = props.myTrainer.data && props.myTrainer.data.length > 0;\n const buttonIsDisabled = () => {\n if (isExistingEntry) return false;\n return !isValid || isSubmitting;\n };\n\n let disabled;\n if (currentGroup === 7 && isExistingEntry) {\n disabled = true;\n if (forceUpdate) {\n disabled = false;\n }\n }\n\n if (!hasTrainer) {\n disabled = false;\n }\n console.log('Disabled?', disabled);\n if (isSubmitting) {\n disabled = true;\n }\n return (\n \n \n {currentGroup === 7 && isExistingEntry && !forceUpdate && hasTrainer && (\n \n Already completed! Talk to your coach if you need to update any\n information.\n \n )}\n {buttonIsDisabled() && (\n \n *Please complete all required questions\n \n )}\n
\n );\n};\n\nStepControlButton.propTypes = {\n groups: PropTypes.array.isRequired,\n myTrainer: PropTypes.object.isRequired,\n formikProps: PropTypes.object.isRequired,\n currentGroup: PropTypes.number.isRequired,\n location: PropTypes.object.isRequired,\n onNextHandler: PropTypes.func.isRequired,\n setSubmitPossible: PropTypes.func.isRequired,\n isExistingEntry: PropTypes.bool.isRequired,\n};\n\nexport default withRouter(StepControlButton);\n","import localStorage from 'modules/local-storage';\n\nconst setLocalStorageOnFieldChange = (value, name, setFieldValue) => {\n setFieldValue(name, value);\n localStorage.setItem(name, value);\n};\n\nconst getSavedValue = (values, name, setFieldValue) => {\n const localValues = { ...values };\n let inputValue = localValues[name];\n\n if (typeof inputValue === 'undefined' || inputValue === '') {\n // Check for a local version\n const localValue = localStorage.getItem(name);\n\n // setFieldTouched(name, true, false);\n\n if (\n inputValue !== localValue &&\n localValue &&\n localValue !== '' &&\n name !== 'phoneNumber'\n ) {\n inputValue = localValue;\n setFieldValue(name, localValue);\n }\n }\n\n return inputValue;\n};\n\nexport { getSavedValue, setLocalStorageOnFieldChange };\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactHtmlParser from 'react-html-parser';\nimport IMAGES from 'modules/assets/web';\nimport { ErrorMessage } from 'formik';\nimport {\n setLocalStorageOnFieldChange,\n getSavedValue,\n} from './handleLocalStorageValues';\nimport DraftWarning from './DraftWarning';\n\nconst SelectField = props => {\n const { formikProps, field, renderRequiredIcon } = props;\n const {\n values,\n handleChange,\n setFieldValue,\n isSubmitting,\n setFieldTouched,\n } = formikProps;\n const {\n name,\n required,\n options,\n caption,\n customClassName = '',\n subCaption,\n } = field;\n\n const inputValue = getSavedValue(values, name, setFieldValue);\n\n return (\n \n {caption !== '' && subCaption !== '' ? (\n
\n ) : null}\n\n
\n

\n
\n
\n {/*
*/}\n
{msg}
}\n name={name}\n />\n \n );\n};\n\nSelectField.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n};\n\nexport default SelectField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { ErrorMessage } from 'formik';\nimport ReactHtmlParser from 'react-html-parser';\n\nimport {\n setLocalStorageOnFieldChange,\n getSavedValue,\n} from './handleLocalStorageValues';\nimport DraftWarning from './DraftWarning';\n\nconst TextField = props => {\n const { formikProps, field, renderRequiredIcon } = props;\n const {\n values,\n handleChange,\n isSubmitting,\n setFieldValue,\n setFieldTouched,\n } = formikProps;\n const { name, required, placeholder, customClassName, caption } = field;\n\n const inputValue = getSavedValue(values, name, setFieldValue);\n return (\n \n {caption !== '' ? (\n
\n ) : null}\n
\n setLocalStorageOnFieldChange(e.target.value, name, setFieldValue)\n }\n disabled={isSubmitting}\n />\n {/*
*/}\n
{msg}
}\n name={name}\n />\n \n );\n};\n\nTextField.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n};\n\nexport default TextField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { ErrorMessage } from 'formik';\nimport {\n setLocalStorageOnFieldChange,\n getSavedValue,\n} from './handleLocalStorageValues';\n\nconst NumberField = props => {\n const { formikProps, field, renderRequiredIcon } = props;\n const {\n values,\n handleChange,\n isSubmitting,\n setFieldValue,\n setFieldTouched,\n } = formikProps;\n const { name, required, caption, placeholder, unit } = field;\n // const inputValue = getSavedValue(values, name, setFieldValue);\n return (\n \n {caption !== '' ? (\n
\n ) : null}\n
\n \n {unit}\n
\n
{msg}
}\n name={name}\n />\n \n );\n};\n\nNumberField.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n};\n\nexport default NumberField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { FaSortDown, FaSortUp } from 'react-icons/fa';\n\nconst UpDownIcon = props => (props.isUp ? : );\n\nUpDownIcon.propTypes = {\n isUp: PropTypes.bool,\n};\n\nUpDownIcon.defaultProps = {\n isUp: false,\n};\nexport default UpDownIcon;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { Field } from 'formik';\n\nconst Checkbox = props => {\n const { name, value, view, disabled } = props;\n let customClassName = '';\n\n switch (view) {\n case 'block':\n customClassName = 'flex-column-reverse block ml-md-2 mr-md-2 p-0';\n break;\n case 'double':\n customClassName = 'double flex-1 col-md-6 col-sm-12 ml-md-2 mr-md-2 p-0';\n break;\n default:\n customClassName = 'mr-2';\n break;\n }\n\n const handleChange = (field, form) => {\n if (field.value.includes(value)) {\n const nextValue = field.value.filter(filedValue => filedValue !== value);\n form.setFieldValue(name, nextValue);\n } else {\n const nextValue = field.value.concat(value);\n form.setFieldValue(name, nextValue);\n }\n };\n\n return (\n \n {({ field, form }) => (\n !disabled && handleChange(field, form)}\n >\n
\n handleChange(field, form)}\n disabled={disabled}\n />\n \n
\n
{value}
\n
\n )}\n \n );\n};\n\nCheckbox.propTypes = {\n name: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n view: PropTypes.string,\n disabled: PropTypes.bool,\n};\n\nCheckbox.defaultProps = {\n view: '',\n disabled: false,\n};\n\nexport default Checkbox;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Editor, EditorState, ContentState, RichUtils } from 'draft-js';\n\nconst BLOCK_TYPES = [\n { label: 'H1', style: 'header-one' },\n { label: 'H2', style: 'header-two' },\n { label: 'H3', style: 'header-three' },\n { label: 'H4', style: 'header-four' },\n { label: 'H5', style: 'header-five' },\n { label: 'H6', style: 'header-six' },\n { label: 'Blockquote', style: 'blockquote' },\n { label: 'UL', style: 'unordered-list-item' },\n { label: 'OL', style: 'ordered-list-item' },\n { label: 'Code Block', style: 'code-block' },\n];\n\nconst INLINE_STYLES = [\n { label: 'Bold', style: 'BOLD' },\n { label: 'Italic', style: 'ITALIC' },\n { label: 'Underline', style: 'UNDERLINE' },\n { label: 'Monospace', style: 'CODE' },\n];\n\nconst styleMap = {\n CODE: {\n backgroundColor: 'rgba(0, 0, 0, 0.05)',\n fontFamily: '\"Inconsolata\", \"Menlo\", \"Consolas\", monospace',\n fontSize: 16,\n padding: 2,\n },\n};\n\nclass RichTextarea extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n editorState: EditorState.createWithContent(\n ContentState.createFromText(props.values[props.fieldName]),\n ),\n };\n }\n\n focus = () => this.refs.editor.focus(); //eslint-disable-line\n\n onChange = editorState => {\n this.setState({ editorState });\n };\n\n onTab = e => {\n const maxDepth = 4;\n this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth));\n };\n\n getBlockStyle = block => {\n switch (block.getType()) {\n case 'blockquote':\n return 'RichEditor-blockquote';\n default:\n return null;\n }\n };\n\n handleKeyCommand = command => {\n const { editorState } = this.state;\n const newState = RichUtils.handleKeyCommand(editorState, command);\n if (newState) {\n this.onChange(newState);\n return true;\n }\n return false;\n };\n\n toggleBlockType = blockType => {\n this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));\n };\n\n toggleInlineStyle = inlineStyle => {\n this.onChange(\n RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle),\n );\n };\n\n render() {\n const { editorState } = this.state;\n const { fieldName, values } = this.props;\n const contentState = editorState.getCurrentContent();\n let className = 'RichEditor-editor';\n\n if (!contentState.hasText()) {\n if (\n contentState\n .getBlockMap()\n .first()\n .getType() !== 'unstyled'\n ) {\n className += ' RichEditor-hidePlaceholder';\n }\n }\n\n return (\n \n );\n }\n}\n\nconst StyleButton = props => {\n const { style, active, label } = props;\n\n const onToggle = e => {\n e.preventDefault();\n props.onToggle(style);\n };\n let className = 'RichEditor-styleButton';\n if (active) {\n className += ' RichEditor-activeButton';\n }\n\n return (\n \n {label}\n \n );\n};\n\nconst BlockStyleControls = props => {\n const { editorState, onToggle } = props;\n const selection = editorState.getSelection();\n const blockType = editorState\n .getCurrentContent()\n .getBlockForKey(selection.getStartKey())\n .getType();\n\n return (\n \n {BLOCK_TYPES.map(type => (\n \n ))}\n
\n );\n};\n\nconst InlineStyleControls = props => {\n const { editorState, onToggle } = props;\n\n const currentStyle = editorState.getCurrentInlineStyle();\n return (\n \n {INLINE_STYLES.map(type => (\n \n ))}\n
\n );\n};\n\nRichTextarea.propTypes = {\n values: PropTypes.object.isRequired,\n fieldName: PropTypes.string.isRequired,\n // handleChange: PropTypes.func.isRequired,\n // rows: PropTypes.number,\n // disabled: PropTypes.bool,\n};\n\nStyleButton.propTypes = {\n style: PropTypes.string.isRequired,\n active: PropTypes.bool,\n label: PropTypes.string.isRequired,\n onToggle: PropTypes.func.isRequired,\n};\n\nBlockStyleControls.propTypes = {\n editorState: PropTypes.object.isRequired,\n onToggle: PropTypes.func.isRequired,\n};\n\nInlineStyleControls.propTypes = {\n editorState: PropTypes.object.isRequired,\n onToggle: PropTypes.func.isRequired,\n};\n\nRichTextarea.defaultProps = {\n // rows: 2,\n // disabled: false,\n};\n\nStyleButton.defaultProps = {\n active: false,\n};\n\nexport default RichTextarea;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactHtmlParser from 'react-html-parser';\nimport { Checkbox } from 'components';\nimport { ErrorMessage } from 'formik';\n\nconst CheckboxField = props => {\n const { formikProps, field, renderRequiredIcon, renderEmptyElement } = props;\n const { isSubmitting } = formikProps;\n const {\n name,\n required,\n labels,\n caption,\n subCaption = '',\n view = '',\n align = 'center',\n } = field;\n\n return (\n \n {caption !== '' ? (\n
\n ) : null}\n
\n {labels.map(item => (\n \n ))}\n {align === 'start' && renderEmptyElement()}\n
\n
{msg}
}\n name={name}\n />\n \n );\n};\n\nCheckboxField.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n renderEmptyElement: PropTypes.func.isRequired,\n};\n\nexport default CheckboxField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport ReactHtmlParser from 'react-html-parser';\nimport Textarea from 'modules/components/web/textarea';\nimport { ErrorMessage } from 'formik';\n\nimport {\n setLocalStorageOnFieldChange,\n getSavedValue,\n} from './handleLocalStorageValues';\nimport DraftWarning from './DraftWarning';\n\nconst TextareaField = props => {\n const { formikProps, field, renderRequiredIcon } = props;\n const {\n values,\n handleChange,\n isSubmitting,\n setFieldValue,\n setFieldTouched,\n } = formikProps;\n const {\n caption,\n name,\n required,\n subCaption = '',\n customClassName = '',\n } = field;\n\n const inputValue = getSavedValue(values, name, setFieldValue);\n return (\n \n
\n
\n );\n};\n\nTextareaField.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n};\n\nexport default TextareaField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nconst SelectField = props => {\n const { field, renderFields } = props;\n const { caption, items } = field;\n\n return (\n \n
\n
\n {renderFields(items)}\n
\n
\n );\n};\n\nSelectField.propTypes = {\n field: PropTypes.object.isRequired,\n renderFields: PropTypes.func.isRequired,\n};\n\nexport default SelectField;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport DatePicker from 'react-datepicker';\nimport moment from 'moment';\n\nimport 'react-datepicker/dist/react-datepicker.css';\n\nclass Datepicker extends React.Component {\n static propTypes = {\n date: PropTypes.any,\n fieldName: PropTypes.string.isRequired,\n setFieldValue: PropTypes.func.isRequired,\n extraValues: PropTypes.any,\n showTimeSelect: PropTypes.bool,\n showYear: PropTypes.bool,\n };\n\n static defaultProps = {\n date: null,\n extraValues: null,\n showTimeSelect: false,\n showYear: false,\n };\n\n constructor(props) {\n super(props);\n let startDate = this.props.date;\n if (this.props.date !== null && moment(this.props.date).isValid()) {\n startDate = moment(this.props.date).toDate();\n } else {\n startDate = null;\n }\n this.state = {\n startDate,\n };\n }\n\n componentDidUpdate(prevProps) {\n // Check if the date has changed\n if (\n moment(this.props.date).isValid() &&\n !moment(this.props.date).isSame(prevProps.date)\n ) {\n this.setState({\n startDate: moment(this.props.date).toDate(),\n });\n }\n }\n\n onChangeRaw = (event) => {\n // Sets the field value so that yup validation can actually handle it and show an error if it is not a valid date\n this.props.setFieldValue(\n this.props.fieldName,\n moment(event.target.value),\n this.props.extraValues,\n );\n if (moment(event.target.value).isValid()) {\n this.setState({\n startDate: moment(event.target.value),\n });\n }\n return null;\n };\n\n handleChange = (date) => {\n // This ensures that the picked date is set as noon of the client's timezone.\n const safeDate = moment(date).isValid() ? date : null;\n\n // const dateAtNoon = setMomentTimeNoon(date, undefined, this.props.utcOffset);\n // console.devLog.info('Date at noon?', dateAtNoon);\n this.setState(\n {\n startDate: safeDate,\n },\n () => {\n this.props.setFieldValue(\n this.props.fieldName,\n moment(date),\n this.props.extraValues,\n );\n },\n );\n };\n\n render() {\n let dateFormatString = 'MMMM d';\n\n if (this.props.showYear) {\n dateFormatString = 'MMMM d, yyyy';\n }\n\n if (this.props.showTimeSelect) {\n dateFormatString = 'MMMM d h:mm aa';\n }\n\n return (\n \n );\n }\n}\n\nexport default Datepicker;\n","import Datepicker from './src/Datepicker';\n\nexport default Datepicker;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Datepicker from 'modules/components/web/datepicker';\nimport { ErrorMessage } from 'formik';\nimport {\n setLocalStorageOnFieldChange,\n getSavedValue,\n} from './handleLocalStorageValues';\n\nconst Calendar = props => {\n const { formikProps, field, renderRequiredIcon } = props;\n const { values, isSubmitting, setFieldValue, setFieldTouched } = formikProps;\n const { name, required, placeholder, caption, customClassName } = field;\n\n const fancySetFieldValue = (key, value) => {\n setLocalStorageOnFieldChange(value, name, setFieldValue);\n };\n\n // const inputValue = getSavedValue(values, name, setFieldValue);\n\n return (\n \n {caption !== '' ? (\n
\n ) : null}\n
\n\n
{msg}
}\n name={name}\n />\n \n );\n};\n\nCalendar.propTypes = {\n formikProps: PropTypes.object.isRequired,\n field: PropTypes.object.isRequired,\n renderRequiredIcon: PropTypes.func.isRequired,\n};\n\nexport default Calendar;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Form, ErrorMessage } from 'formik';\nimport ReactHtmlParser from 'react-html-parser';\nimport IMAGES from 'modules/assets/web';\nimport PhoneNumberInput from 'modules/components/web/phone-number-input';\nimport StepControlButton from './StepControl';\nimport {\n SelectField,\n TextField,\n NumberField,\n CheckboxField,\n TextareaField,\n FieldsArray,\n DateField,\n} from './CustomFields';\n\nclass OnboardingForm extends React.Component {\n renderEmptyElement = () => (\n \n );\n\n renderRequiredIcon = () => (\n \n
\n \n );\n\n renderGroup = group => {\n const { name, fields } = group;\n\n return (\n \n
\n {this.renderFields(fields)}\n
\n
\n );\n };\n\n renderFields = fields => fields.map(field => this.renderField(field));\n\n renderField = field => {\n const { type } = field;\n const { formikProps } = this.props;\n\n switch (type) {\n case 'select':\n return (\n \n );\n case 'number':\n return (\n \n );\n case 'text':\n return (\n \n );\n case 'multiselect':\n return (\n \n );\n case 'textarea':\n return (\n \n );\n case 'fieldsArray':\n return (\n \n );\n case 'date':\n return (\n \n );\n\n case 'phone':\n return (\n \n
\n
\n
\n Must include country code; US is +1\n \n
{msg}
}\n name={field.name}\n />\n \n );\n default:\n break;\n }\n return null;\n };\n\n render() {\n // console.devLog.info('Onboarding values:', this.props.formikProps);\n const { groups, currentGroup } = this.props;\n\n return (\n \n );\n }\n}\n\nOnboardingForm.propTypes = {\n groups: PropTypes.array.isRequired,\n formikProps: PropTypes.object.isRequired,\n currentGroup: PropTypes.number.isRequired,\n onNextHandler: PropTypes.func.isRequired,\n setSubmitPossible: PropTypes.func.isRequired,\n isExistingEntry: PropTypes.bool.isRequired,\n myTrainer: PropTypes.object.isRequired,\n};\n\nexport default OnboardingForm;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport IMAGES from 'modules/assets/web';\n\nconst OnboardingFooter = props => {\n const {\n isHiddenSidebar,\n toggleSidebar,\n navigateToStep,\n isCompletedStep,\n } = props;\n const renderStep = (stepName, stepNumber) => (\n \n navigateToStep(stepNumber + 1)}>\n {stepNumber + 1}\n : {stepName}\n \n \n );\n\n const steps = [\n 'Basics',\n 'About You',\n 'Change',\n 'Fitness',\n 'Nutrition',\n 'Mindset',\n 'Finish',\n ];\n\n return (\n \n );\n};\n\nOnboardingFooter.propTypes = {\n isHiddenSidebar: PropTypes.bool,\n toggleSidebar: PropTypes.func.isRequired,\n navigateToStep: PropTypes.func.isRequired,\n isCompletedStep: PropTypes.func.isRequired,\n};\n\nOnboardingFooter.defaultProps = {\n isHiddenSidebar: false,\n};\n\nexport default OnboardingFooter;\n","import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport IMAGES from 'modules/assets/web';\nimport { Link } from 'react-router-dom';\nimport { Modal, ModalBody } from 'reactstrap';\n\nconst CompletedQuestionnaireModal = props => {\n const [isVisible, setIsVisible] = useState(true);\n return (\n \n \n \n

\n
\n Phase One complete, {props.myClient.data[0].firstName}! Thanks for\n all the info!\n
\n
\n We{`’`}ll use these answers along with the notes from your initial\n call with to match you up with a Nerd Fitness Coach who has the\n strengths to help you succeed! Once you’re up on the waiting list,\n we’ll be in touch with more information about your Coach and then\n the real fun begins!\n
\n
\n For now: deep breaths, 5 burpees, and click below for a message from\n Rebel One!\n
\n\n
setIsVisible(false)}\n >\n
\n \n
\n \n \n );\n};\n\nCompletedQuestionnaireModal.propTypes = {\n // title: PropTypes.string,\n // handleHideModal: PropTypes.func.isRequired,\n myClient: PropTypes.object.isRequired,\n};\n\nCompletedQuestionnaireModal.defaultProps = {\n // title: 'Whoohoo, all done!',\n};\n\nexport default CompletedQuestionnaireModal;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport queryString from 'query-string';\nimport { withRouter } from 'react-router-dom';\nimport { Formik } from 'formik';\nimport * as yup from 'yup';\nimport moment from 'moment';\nimport { isArray } from 'lodash';\nimport { noteBoxTypes } from 'modules/enums/notebox-types';\nimport { onboardingFormData } from 'modules/form-data';\n\nimport Toast from 'modules/toast';\n\nimport apiFetch from 'modules/api-fetch';\nimport IMAGES from 'modules/assets/web';\n\nimport {\n isExistingOnboardingEntry,\n hasLegacyOnboardingData,\n} from 'modules/functions/src/hasExistingOnboardingData';\nimport DisplayLegacyOnboardingEntry from 'modules/components/web/DisplayLegacyOnboardingEntry';\n\nimport OnboardingHeaderBg from './OnboardingHeader';\nimport OnboardingForm from './OnboardingForm';\nimport OnboardingFooter from './OnboardingFooter';\n\nimport CompletedQuestionnaireModal from './CompletedQuestionnaireModal';\n\nclass Onboarding extends React.Component {\n static propTypes = {\n myQuestionnaire: PropTypes.object.isRequired,\n myClient: PropTypes.object.isRequired,\n myTrainer: PropTypes.object.isRequired,\n toggleSidebar: PropTypes.func.isRequired,\n isHiddenSidebar: PropTypes.bool,\n };\n\n static defaultProps = {\n isHiddenSidebar: false,\n };\n\n constructor(props) {\n super(props);\n\n this.state = {\n currentGroup: 1,\n visited: [true, false, false, false, false, false, false],\n isSubmitPossible: false,\n showOnboardingModal: false,\n isExistingEntry: null,\n };\n this.onboardingRef = React.createRef();\n }\n\n componentDidMount = () => {\n if (isExistingOnboardingEntry(this.props.myQuestionnaire)) {\n this.setState({\n visited: [true, true, true, true, true, true, true],\n });\n }\n };\n\n componentDidUpdate = (prevProps, prevState) => {\n if (prevState.currentGroup !== this.state.currentGroup) {\n this.onboardingRef.current.scrollTop = 0;\n }\n };\n\n setInitialValues = () => {\n if (isExistingOnboardingEntry(this.props.myQuestionnaire)) {\n return JSON.parse(this.props.myQuestionnaire.data[0].questionnaire);\n }\n\n const initialValues = {\n heightFeet: '',\n heightInches: '',\n pronouns: '',\n daysWorkOut: '',\n daysCommitWorkout: '',\n daysBestWorkOuts: '',\n currentTrainingProgram: '',\n completingWorkOuts: '',\n availableWorkOuts: [],\n pastWorkOuts: '',\n nutritionalExperience: '',\n mostEatMeals: '',\n specificDietaryRestriction: '',\n biggestPastNutrition: '',\n cigarettesPerWeek: '',\n drinksPerWeek: '',\n yourHousehold: '',\n howMuchSupport: '',\n existingPractitionerSupport: '',\n currentStressLevel: [],\n bigProjectMostHelpful: '',\n whichDescribesYouBest: '',\n succeededInChangingHabit: '',\n additionalEquipment: '',\n desiredCoachNumber: '',\n phoneNumber: '',\n foundSuccess: '',\n nerdyHobbies: '',\n wantToKnowAboutCoach: '',\n hoursSleepNight: '',\n mailingAddress1: '',\n mailingAddress2: '',\n state: '',\n city: '',\n country: '',\n zip: '',\n };\n\n return initialValues;\n };\n\n setVisited = (stepNumber) => {\n const { visited } = this.state;\n\n visited[stepNumber] = true;\n this.setState({\n visited,\n });\n };\n\n setSubmitPossible = (formikProps) => {\n this.setState(\n {\n isSubmitPossible: true,\n },\n () => {\n this.submitForm(formikProps.values, formikProps);\n },\n );\n };\n\n calculateHeight = (values) => {\n // If feet and inches are set, full calculations\n // If either is null, just use that\n let retval = null;\n if (values.heightFeet && values.heightInches) {\n retval = values.heightFeet * 12 + values.heightInches;\n } else if (values.heightFeet) {\n retval = values.heightFeet * 12;\n }\n\n return retval;\n };\n\n emailMessages = () => {\n const message = `\n High five, ${this.props.myClient.data[0].firstName}! You're the best!
\n Thanks for telling us a little more about yourself. We’ll use the info you provided along with the notes from your initial call to match you up with a Nerd Fitness Coach who has the strengths to help you succeed! Once you’re up on the waiting list, we’ll be in touch with more information about your Coach.
\n I also wanted to remind you that, as a benefit for joining the Nerd Fitness Coaching Family, you’ve also gained complimentary access to Nerd Fitness Prime.
\n Nerd Fitness Prime is separate from Nerd Fitness Coaching, and includes self-paced programming that users can follow on their journeys to get healthy.
\n In Coaching, you’ll of course have a mentor to guide you through the process and build tasks collaboratively that fit your specific lifestyle and goals.
\n If you want to completely ignore NF Prime and wait to chat with your Coach, that’s totally fine. You can stop reading this email right now, do 5 push ups, and keep an eye on your email inbox for our recommended Coach pairing.
\n However, if you’d like to start exploring some of that self-paced content for ideas or strategies, you can utilize Nerd Fitness Prime at any time using the same login info you just created. If anything looks appealing, be sure to mention it to your Coach so you two can talk through the best ways to incorporate it into your plan. You will maintain complimentary access for as long as you’re a member of NF Coaching.
\n We’ll be in touch soon, but if you have any questions don’t hesitate to touch base by replying to this email!
\n For the Rebellion!
\n -Team Nerd Fitness
`;\n\n /* \n So does this waiting period give you permission to just sit around and wait for your name to be called? Heck no! To get a jump start on your training, we'd like to invite you to join our Nerd Fitness Academy and begin reading through the Mindset and Nutrition modules; your Coach will embrace many of these philosophies in your programming! Already a member? Awesome! Give those sections a skim as a refresher.\n
\n Click below to set up your complimentary account. Choose the gender you identify with most.\n
\n \n
\n \n \n While you wait to hear from us, take 10 minutes of self-reflection and consider this bonus: when the going gets tough (or stressful or busy) and prioritizing yourself is the LAST thing on your mind, how can your Coach best serve you?\n
\n */\n\n this.sendEmail('You did it!', message, this.props.myClient.data[0].email);\n\n const messageRecipient =\n this.props.myTrainer.data.length > 0 &&\n this.props.myTrainer.data &&\n this.props.myTrainer.data[0].email\n ? this.props.myTrainer.data[0].email\n : 'coaching@nerdfitness.com';\n\n const message2 = `${this.props.myClient.data[0].firstName} ${this.props.myClient.data[0].lastName} finished the onboarding questionnaire. Email: ${this.props.myClient.data[0].email}
Hop to it!
`;\n\n this.sendEmail('Questionnaire done', message2, messageRecipient);\n };\n\n sendEmail = async (subject, body, destination) => {\n const config = {\n url: 'Client/SendEmail',\n method: 'POST',\n body: {\n body,\n subject,\n email: destination,\n },\n };\n try {\n // console.devLog.info('Sending ze email', config);\n await apiFetch(config);\n } catch (e) {\n console.log('Config??', config);\n console.error('Error sending the email:', e);\n }\n };\n\n submitForm = async (values, formikProps) => {\n const { isSubmitPossible, currentGroup } = this.state;\n const { forceUpdate } = queryString.parse(this.props.location.search, {\n ignoreQueryPrefix: true,\n });\n const hasPreviouslySubmitted = isExistingOnboardingEntry(\n this.props.myQuestionnaire,\n );\n\n // const clientHasLegacyOnboardingData = hasLegacyOnboardingData(\n // this.props.myQuestionnaire,\n // );\n if (isSubmitPossible && currentGroup === 7) {\n const url =\n isExistingOnboardingEntry(this.props.myQuestionnaire) ||\n this.state.isExistingEntry\n ? 'Client/UpdateQuestionnaire'\n : 'Client/CreateQuestionnaire';\n\n const body = { ...this.props.myQuestionnaire.data[0] };\n body.questionnaire = JSON.stringify(values);\n\n const config = {\n url,\n method: 'POST',\n body,\n };\n\n // TODO - if we just created it and the user might change anything then hit update, we need to handle that.\n // Probably just navigate them away from this page to get around that problem.\n try {\n await apiFetch(config);\n // Send the values to the update funciton so it can combine existing data and new information into the client model\n\n if (!forceUpdate) {\n // Email coaching that this client has completed onboarding\n // also sends a success message to the client\n this.emailMessages();\n }\n\n this.updateClientInfo(values);\n this.updateClientBoxes(values);\n Toast.success('Submitted!');\n // Submit their onboarding survey to an endpoint to update their profile\n // Redirect the user to chat\n // this.props.onUpdated();\n // throw up a modal that tells the user they're all done and redirects them on close on matter what\n this.setState({\n showOnboardingModal: true,\n isExistingEntry: true,\n });\n if (hasPreviouslySubmitted) {\n formikProps.setSubmitting(false);\n }\n } catch (err) {\n Toast.error(`Error submitting message: ${err}`);\n }\n // Don't uncomment this until the above comment is addressed, otherwise uses could hit the create endpoint more than once\n // formikProps.setSubmitting(false);\n } else formikProps.setSubmitting(false);\n };\n\n updateClientInfo = async (values) => {\n // console.devLog.info('Update client info body??');\n const clientInfo = {\n ...this.props.myClient.data[0],\n ...values,\n height: this.calculateHeight(values),\n questionnaireVersion: onboardingFormData.version,\n utcOffset: moment().utcOffset(),\n };\n\n const config = {\n url: 'Client/UpdateClient',\n method: 'POST',\n body: clientInfo,\n };\n\n // console.devLog.info('Config:', config);\n try {\n await apiFetch(config);\n } catch (err) {\n Toast.error(`Error submitting message: ${err}`);\n }\n };\n\n updateClientBoxes = async (values) => {\n // Map existing onboarding field to notebox types\n const fieldBoxMap = {\n nerdyHobbies: 'rapportNotes',\n injuries: 'currentInjuries',\n daysWorkOut: 'workoutHabits',\n daysCommitWorkout: 'workoutHabits',\n daysBestWorkOuts: 'workoutHabits',\n currentTrainingProgram: 'workoutHabits',\n availableWorkOuts: 'equipment',\n additionalEquipment: 'equipment',\n pastWorkOuts: 'struggles',\n nutritionalExperience: 'nutritionHabits',\n comfortableInTheKitchen: 'nutritionHabits',\n mostEatMeals: 'nutritionHabits',\n specificDietaryRestriction: 'healthNotes',\n biggestPastNutrition: 'struggle',\n cigarettesPerWeek: 'lifestyleHabits',\n drinksPerWeek: 'lifestyleHabits',\n hoursSleepNight: 'lifestyleHabits',\n yourHousehold: 'lifestyleHabits',\n howMuchSupport: 'lifestyleHabits',\n existingPractitionerSupport: 'lifestyleHabits',\n desiredCoachNumber: 'rapportNotes',\n foundSuccess: 'nutritionHabits',\n wantToKnowAboutCoach: 'rapportNotes',\n\n // New\n focusesPassions: 'rapportNotes',\n importantPeoplePets: 'rapportNotes',\n interestingFact: 'rapportNotes',\n characterLike: 'rapportNotes',\n attractedNFCoaching: 'rapportNotes',\n nervousAboutJourney: 'rapportNotes',\n coachExpectations: 'rapportNotes',\n coachAccountability: 'retentionNotes',\n obstacles: 'struggles',\n coachSupportTough: 'rapportNotes',\n startingCoupleWeeksComingUp: 'struggles',\n adjustmentConfidence: 'struggles',\n fitnessExperience: 'workoutHabits',\n fitnessSuccessFirstMonth: 'goals',\n extraEquipmentLearnOrDislike: 'equipment',\n exerciseActivitiesRegular: 'workoutHabits',\n concerns: 'healthNotes',\n scaleChangeFitnessHabits: 'workoutHabits',\n workoutLocation: 'workoutHabits',\n averageDayEating: 'nutritionHabits',\n nutritionSuccessFirstMonth: 'goals',\n nutritionFutureGoals: 'goals',\n nutritionChanges: 'nutritionHabits',\n scaleEnjoyCooking: 'nutritionHabits',\n preparesMeals: 'nutritionHabits',\n eatingStress: 'nutritionHabits',\n unhealthyFoodRelationships: 'nutritionHabits',\n troubleAvoidingFoods: 'nutritionHabits',\n foodEatMoreDislike: 'nutritionHabits',\n scaleReadyNutritionHabits: 'nutritionHabits',\n lifestyleMindsetHabits: 'lifestyleHabits',\n scaleSleepQuality: 'lifestyleHabits',\n travel: 'lifestyleHabits',\n stressCatalyst: 'lifeStyleHabits',\n averageWeekendTime: 'lifestyleHabits',\n scaleMindsetChanges: 'lifestyleHabits',\n prioritiesFirst: 'quickSummary',\n prioritiesSecond: 'quickSummary',\n bigProjectMostHelpful: 'retentionNotes',\n whichDescribesYouBest: 'retentionNotes',\n succeededInChangingHabit: 'retentionNotes',\n };\n\n const valuesWithMappedKeys = {};\n\n // Take each of the values. If it matches a key in the fieldBoxMap then replace the value's key with the notebox key.\n // Then do an API call for each one that needs it to create an entry\n Object.entries(values).forEach((entry) => {\n // transform the keys\n // console.devLog.info('Entry...', entry);\n let key = entry[0];\n const originalKey = entry[0];\n if (key in fieldBoxMap) {\n // console.devLog.info('Key is found in fieldBoxMap:', entry[0]);\n // console.devLog.info('New key:', fieldBoxMap[entry[0]]);\n // eslint-disable-next-line prefer-destructuring\n valuesWithMappedKeys[fieldBoxMap[entry[0]]] = entry[1];\n // Replace the existing key for the object with our correct one for the notebox types\n key = fieldBoxMap[entry[0]];\n }\n\n if (entry[1] === '' || entry[1].length <= 0 || !(key in noteBoxTypes)) {\n // Do nothing\n } else {\n // Create a notebox entry for this response, lets gooooo\n let value = entry[1];\n if (isArray(entry[1])) {\n value = entry[1].join();\n }\n value = `${originalKey} : ${value}`;\n const body = {\n clientId: this.props.myClient.data[0].id,\n section: noteBoxTypes[key],\n notes: value,\n };\n\n const config = {\n url: 'Client/CreateTrainerNotes',\n method: 'POST',\n body,\n };\n\n try {\n apiFetch(config);\n } catch (e) {\n console.devLog.info('Error saving notebox entry:', e);\n }\n }\n\n //\n });\n };\n\n validationSchema = () => {\n const { currentGroup } = this.state;\n\n switch (currentGroup) {\n case 1:\n return yup.object().shape({\n pronouns: yup.string().required('Required question'),\n nickname: yup.string().required('Required question'),\n birthday: yup.object().momentDate().required('Required question'),\n heightFeet: yup.number(),\n mailingAddress1: yup.string().required('Required question'),\n city: yup.string().required('Required question'),\n zip: yup.string().required('Required question'),\n heightInches: yup.number(),\n phoneNumber: yup\n .string()\n .phone()\n .required(\n 'Must be a valid phone number. Please include country code for non-US numbers',\n ),\n unitPreferences: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n // .phone('Must be a valid phone number with country code'),\n });\n case 2:\n return yup.object().shape({\n focusesPassions: yup.string().required('Required question'),\n importantPeoplePets: yup.string().required('Required question'),\n nerdyHobbies: yup.string(),\n });\n case 3:\n return yup.object().shape({\n attractedNFCoaching: yup.string().required('Required question'),\n nervousAboutJourney: yup.string().required('Required question'),\n excitedAbout: yup.string().required('Required question'),\n retentionNotes: yup.string().required('Required question'),\n desiredCoachNumber: yup.string().required('Required question'),\n whichDescribesYouBest: yup.string(),\n bigProjectMostHelpful: yup.string(),\n succeededInChangingHabit: yup.string(),\n coachExpectations: yup.string().required('Required question'),\n // coachAccountability: yup.string().required('Required question'),\n obstacles: yup.string().required('Required question'),\n coachSupportTough: yup.string().required('Required question'),\n startingCoupleWeeksComingUp: yup\n .string()\n .required('Required question'),\n });\n // FITNESS\n case 4:\n return yup.object().shape({\n fitnessExperience: yup.string().required('Required question'),\n pastWorkOuts: yup.string().required('Required question'),\n fitnessSuccessFirstMonth: yup.string().required('Required question'),\n goals: yup.string().required('Required question'),\n timePerWorkout: yup.string().required('Required question'),\n workoutLocation: yup.string().required('Required question'),\n additionalEquipment: yup.string().required('Required question'),\n extraEquipmentLearnOrDislike: yup\n .string()\n .required('Required question'),\n exerciseActivitiesRegular: yup.string().required('Required question'),\n injuries: yup.string().required('Required question'),\n scaleChangeFitnessHabits: yup.string().required('Required question'),\n daysWorkOut: yup.mixed().nullable(true).required('Required question'),\n daysCommitWorkout: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n daysBestWorkOuts: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n });\n // Nutrition\n case 5:\n return yup.object().shape({\n // nutritionalExperience: yup.string().required('Required question'),\n comfortableInTheKitchen: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n biggestPastNutrition: yup.string().required('Required question'),\n foundSuccess: yup.string().required('Required question'),\n averageDayEating: yup.string().required('Required question'),\n nutritionSuccessFirstMonth: yup\n .string()\n .required('Required question'),\n nutritionFutureGoals: yup.string().required('Required question'),\n nutritionChanges: yup.string().required('Required question'),\n scaleEnjoyCooking: yup.string().required('Required question'),\n preparesMeals: yup.string().required('Required question'),\n specificDietaryRestriction: yup\n .string()\n .required('Required question'),\n scaleReadyNutritionHabits: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n });\n // Lifestyle-mindset\n case 6:\n return yup.object().shape({\n cigarettesPerWeek: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n drinksPerWeek: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n hoursSleepNight: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n scaleSleepQuality: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n currentStressLevel: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n scaleMindsetChanges: yup\n .mixed()\n .nullable(true)\n .required('Required question'),\n yourHousehold: yup.string().required('Required question'),\n howMuchSupport: yup.string().required('Required question'),\n existingPractitionerSupport: yup\n .string()\n .required('Required question'),\n lifestyleMindsetHabits: yup.string().required('Required question'),\n travel: yup.string().required('Required question'),\n stressCatalyst: yup.string().required('Required question'),\n averageWeekdayTime: yup.string().required('Required question'),\n averageWeekendTime: yup.string().required('Required question'),\n });\n case 7:\n return yup.object().shape({});\n default:\n return null;\n }\n };\n\n stepMoveForward = () => {\n const { visited, currentGroup } = this.state;\n // console.devLog.info('Step move forward formik', formikProps);\n // formikProps.validateForm();\n visited[currentGroup] = true;\n this.setState({\n currentGroup: currentGroup + 1,\n visited,\n });\n };\n\n navigateToStep = (stepNumber) => {\n const { visited } = this.state;\n console.devLog.info('Navigate to step..', stepNumber);\n if (visited[stepNumber - 1]) {\n this.setState({ currentGroup: stepNumber });\n }\n };\n\n isCompletedStep = (stepNumber) => {\n const { currentGroup } = this.state;\n\n const result = currentGroup >= stepNumber ? ' bg-primary' : '';\n return result;\n };\n\n render() {\n const { groups } = onboardingFormData;\n const { toggleSidebar, isHiddenSidebar } = this.props;\n const { currentGroup } = this.state;\n\n console.devLog.info('Current group?', currentGroup);\n // console.devLog.info('Onboarding all props:', this.props);\n const clientHasLegacyOnboardingData = hasLegacyOnboardingData(\n this.props.myQuestionnaire,\n );\n const isExistingOnboardingEntryBool = isExistingOnboardingEntry(\n this.props.myQuestionnaire,\n );\n // console.devLog.info('Is existing??', isExistingOnboardingEntryBool);\n return (\n \n
\n {this.state.showOnboardingModal && (\n
\n )}\n
\n

\n {clientHasLegacyOnboardingData && currentGroup === 1 && (\n
\n \n
\n )}\n
{\n // console.log('FP?', formikProps);\n return (\n \n );\n }}\n />\n \n
\n
\n );\n }\n}\n\nexport default withRouter(Onboarding);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { compose, wrapDisplayName } from 'recompose';\n\nimport { withFetch, withLoading } from 'modules/with-fetch';\nimport queryString from 'query-string';\n\nimport {\n isExistingOnboardingEntry,\n hasLegacyOnboardingData,\n} from 'modules/functions/src/hasExistingOnboardingData';\nimport CompleteWaiver from './CompleteWaiver';\nimport Onboarding from './Onboarding';\n\nclass OnboardingWrapper extends React.Component {\n state = {\n checkingWaiver: false,\n };\n\n // We need to check every 15 seconds to see if the waiver has been completed\n componentDidMount() {\n if (!this.hasCompletedWaiver()) {\n this.waiverCheckInterval = setInterval(() => {\n this.waiverCheck();\n }, 10000);\n this.hideChecking = setInterval(() => {\n this.setState({\n checkingWaiver: false,\n });\n }, 20000);\n }\n }\n\n componentWillUnmount() {\n clearInterval(this.waiverCheckInterval);\n clearInterval(this.hideChecking);\n }\n\n waiverCheck = () => {\n // console.devLog.info('Checking to see if the waiver is completed now...');\n this.setState(\n {\n checkingWaiver: true,\n },\n () => this.props.myWaiver.refreshData(false),\n );\n };\n\n hasCompletedWaiver = () =>\n this.props.myWaiver &&\n this.props.myWaiver.data.length > 0 &&\n this.props.myWaiver.data[0].completed;\n\n render() {\n // If the waiver isn't completed, we need the person to do that\n const clientHasExistingOnboardingEntry = isExistingOnboardingEntry(\n this.props.myQuestionnaire,\n );\n const clientHasLegacyOnboardingData = hasLegacyOnboardingData(\n this.props.myQuestionnaire,\n );\n let localHideSidebar = this.state.isHiddenSidebar;\n // Hide the sidebar if the client doesn't have any onboarding information\n if (!clientHasExistingOnboardingEntry && !clientHasLegacyOnboardingData) {\n // console.devLog.info('Uhhh, ');\n localHideSidebar = true;\n }\n\n const { bypassWaiver } = queryString.parse(this.props.location.search, {\n ignoreQueryPrefix: true,\n });\n\n if (\n !clientHasLegacyOnboardingData &&\n !this.hasCompletedWaiver() &&\n !bypassWaiver\n ) {\n // console.devLog.info('User needs to complete waiver');\n return (\n \n );\n }\n return (\n \n \n
\n );\n }\n}\n\nOnboardingWrapper.propTypes = {\n myQuestionnaire: PropTypes.object.isRequired,\n myWaiver: PropTypes.object.isRequired,\n myClient: PropTypes.object.isRequired,\n};\n\nconst EnhancedComponent = compose(\n withFetch('Client/MyQuestionnaire', undefined, 'myQuestionnaire'),\n withFetch('Client/MyClient', undefined, 'myClient'),\n withFetch('Client/MyTrainer', undefined, 'myTrainer'),\n withFetch('Client/MyWaiver', undefined, 'myWaiver'),\n withLoading('myQuestionnaire'),\n withLoading('myClient'),\n withLoading('myTrainer'),\n withLoading('myWaiver', undefined, undefined, {\n position: 'absolute',\n left: '50%',\n top: '20px',\n }),\n)(OnboardingWrapper);\n\nEnhancedComponent.displayName = wrapDisplayName(OnboardingWrapper, 'withFetch');\n\nexport default EnhancedComponent;\n","import React from 'react';\n\nconst Footer = () => (\n \n);\n\nexport default Footer;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\n\n// Renders a NavItem with active class based on current path\nclass ActiveNavItem extends React.Component {\n static propTypes = {\n path: PropTypes.string.isRequired,\n location: PropTypes.object.isRequired,\n children: PropTypes.node.isRequired,\n };\n\n isActive = () => {\n const { pathname } = this.props.location;\n\n return pathname.includes(this.props.path) ? 'active' : '';\n };\n\n render() {\n return (\n {this.props.children}\n );\n }\n}\n\nexport default withRouter(ActiveNavItem);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Link, withRouter } from 'react-router-dom';\nimport { compose, wrapDisplayName } from 'recompose';\n\nimport { withFetch, withLoading } from 'modules/with-fetch';\nimport IMAGES from 'modules/assets/web';\nimport {\n isExistingOnboardingEntry,\n hasLegacyOnboardingData,\n} from 'modules/functions/src/hasExistingOnboardingData';\nimport Loading from 'modules/loading';\n\nimport ActiveNavItem from './ActiveNavItem';\n\nclass ExistingEntryItems extends React.Component {\n static propTypes = {\n myQuestionnaire: PropTypes.object.isRequired,\n myTrainer: PropTypes.object.isRequired,\n onClick: PropTypes.func,\n };\n\n static defaultProps = {\n onClick: () => {},\n };\n\n // The user must both have completed onboarding (or legacy onboarding) AND have a trainer assigned.\n conditionallyDisplayEntryCompletedItems = () =>\n (isExistingOnboardingEntry(this.props.myQuestionnaire) ||\n hasLegacyOnboardingData(this.props.myQuestionnaire)) &&\n this.props.myTrainer.data &&\n this.props.myTrainer.data.length > 0;\n\n render() {\n return (\n \n {/* \n \n
\n Profile\n \n \n */}\n {this.conditionallyDisplayEntryCompletedItems() && (\n \n \n
\n Chat\n \n \n )}\n \n );\n }\n}\n\nconst LoadingWithDiv = () => (\n \n \n
\n);\n\nconst EnhancedComponent = compose(\n withFetch('Client/MyQuestionnaire', undefined, 'myQuestionnaire'),\n withFetch('Client/MyTrainer', undefined, 'myTrainer'),\n withLoading('myQuestionnaire', LoadingWithDiv),\n withLoading('myTrainer', LoadingWithDiv),\n)(ExistingEntryItems);\n\nEnhancedComponent.displayName = wrapDisplayName(\n ExistingEntryItems,\n 'withFetch',\n);\n\n// This is needed otherwise router updates don't get passed to ActiveNavItem\n// https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md\nexport default withRouter(EnhancedComponent);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport IMAGES from 'modules/assets/web';\n\nimport ExistingEntryItems from './ExistingEntryItems';\nimport ActiveNavItem from './ActiveNavItem';\n\nclass NavItems extends React.Component {\n static propTypes = {\n onClick: PropTypes.func,\n };\n\n static defaultProps = {\n onClick: () => {},\n };\n\n render() {\n return (\n \n \n \n \n
\n Onboarding\n \n \n \n );\n }\n}\n\nexport default NavItems;\n","import React from 'react';\nimport { IoIosMenu, IoMdClose } from 'react-icons/io';\n\nimport { SignOutButton } from 'modules/auth';\nimport IMAGES from 'modules/assets/web';\n\nimport NavItems from '../nav/NavItems';\n\nclass TopHeader extends React.Component {\n state = {\n showDropdown: false,\n };\n\n toggleDropdown = () => {\n this.setState({\n showDropdown: !this.state.showDropdown,\n });\n };\n\n render() {\n const { showDropdown } = this.state;\n\n const customClassName = showDropdown ? 'show' : 'hide';\n\n return (\n \n );\n }\n}\n\nexport default TopHeader;\n","import React from 'react';\nimport { SignOutButton } from 'modules/auth';\nimport IMAGES from 'modules/assets/web';\n\nimport NavItems from '../nav/NavItems';\n\nclass SideNavigation extends React.Component {\n render() {\n return (\n \n );\n }\n}\n\nexport default SideNavigation;\n","import React from 'react';\nimport { Route, Switch, Redirect } from 'react-router-dom';\nimport { ToastContainer } from 'react-toastify';\n\nimport { PrivateRoute } from 'modules/auth';\nimport ErrorBoundary from 'modules/error-boundary';\nimport Chat from 'modules/components/web/chat/src/ClientFacing/ChatClient';\n\nimport SignIn from 'pages/SignIn/SignIn';\nimport SetPassword from 'pages/SetPassword/SetPassword';\nimport ResetPassword from 'pages/ResetPassword/ResetPassword';\nimport QuestionnaireComplete from 'pages/Onboarding/QuestionnaireComplete';\nimport OnboardingWrapper from 'pages/Onboarding/OnboardingWrapper';\nimport Footer from '../footer/Footer';\nimport TopHeader from '../header/TopHeader';\nimport SideNavigation from '../sidebar/SideNavigation';\n\nexport default class Main extends React.Component {\n state = { hideSidebar: false };\n\n toggleSidebar = () => {\n this.setState(prevState => ({ hideSidebar: !prevState.hideSidebar }));\n };\n\n // Switch is used to control when sidebar/topheader shouldn't be rendered (e.g. for unauthenticated pages)\n render() {\n return (\n \n
\n \n null} />\n null} />\n null} />\n \n \n \n null} />\n null} />\n null} />\n\n \n \n \n (\n \n )}\n />\n \n \n \n \n \n \n \n \n \n
\n
\n );\n }\n}\n","import React from 'react';\nimport { Helmet } from 'react-helmet';\nimport { Provider } from 'mobx-react';\n\n\nimport { FetchCacheStore } from 'modules/with-fetch';\nimport { LogPageView } from 'modules/ga';\n\nimport Main from 'layout/main/Main';\n\nconst App = () => (\n \n \n \n \n \n My NF Coaching\n \n \n \n \n \n \n \n
\n \n);\n\nexport default App;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { BrowserRouter } from 'react-router-dom';\n\nimport './modules/styles/src/web/shared/index.css';\nimport './modules/styles/src/web/client/index.css';\nimport './config/config';\nimport App from './App';\n// import registerServiceWorker from './registerServiceWorker';\n\n// Wrap app to help capture errors\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root'),\n);\n// registerServiceWorker();\n"],"sourceRoot":""}