}\n */\n const validate = async paymentProcessor => {\n setErrorMessage(null);\n setPaypalErrorMessage(null);\n\n // First, reset the error state. The state will be repopulated when we execute validation.\n validator.current.hideMessages();\n setShippingStageState({ ...shippingStageState });\n setGiftSenderInformation({ ...giftSenderInformation });\n\n // Stripe does not support all of the countries that we ship to.\n if (paymentProcessor === \"stripe\" && !country.supportsStripe) {\n setErrorMessage(\"Sorry, we don't accept credit card payments from your country. Please use PayPal to place your order.\");\n return false;\n }\n\n if (validator.current.allValid()) {\n if (createAccountEnabled) {\n if (npi && npi.trim().length) {\n try {\n await npiValidationApi.validateNpi(npi);\n } catch (response) {\n setErrorMessage(`NPI validation issue: ${response.error}`);\n return false;\n }\n }\n\n try {\n await usersApi.validateNewParams({...shippingStageState}, deferredUserCreate);\n }\n catch (response) {\n setErrorMessage(`There was an issue creating your account: ${response.error}`);\n return false;\n }\n }\n\n if (clinicianMode === CLINICIAN_MODES.EXISTING_ORGANIZATION) {\n let result = false;\n\n try {\n result = await organizationsApi.validateOrganizationCode(shippingStageState.organizationCode);\n } catch (response) { }\n\n if (!result) {\n setErrorMessage(\"The organization code you have entered is not valid.\");\n return false;\n }\n }\n\n return true;\n }\n\n validator.current.showMessages();\n setShippingStageState({ ...shippingStageState });\n setGiftSenderInformation({ ...giftSenderInformation });\n return false;\n };\n\n const validateAddress = async () => {\n if (shippingStageState.country.code !== \"US\") {\n // we only interested in validating US addresses\n return true;\n }\n setErrorMessage(null);\n setPaypalErrorMessage(null);\n\n // First, reset the error state. The state will be repopulated when we execute validation.\n validator.current.hideMessages();\n\n try {\n const { validatedAddress } = await addressValidationApi.validateAddress({\n name: `${shippingStageState.firstName} ${shippingStageState.lastName}`,\n phone: shippingStageState.phone,\n addressLine1: shippingStageState.address1,\n addressLine2: shippingStageState.address2,\n cityLocality: shippingStageState.city,\n stateProvince: shippingStageState.state,\n postalCode: shippingStageState.zipCode,\n countryCode: shippingStageState.country.code,\n });\n\n if (validatedAddress.status === \"verified\") {\n return true;\n } else {\n setErrorMessage(\"There was an issue verifying your address. Please ensure your address information is correct and try again.\");\n return false;\n }\n } catch (_) {}\n // Do not stop the order process because of the address validation.\n return true;\n };\n\n /**\n * Applicable for Stripe orders only. This method will ensure all fields are filled in,\n * and will then proceed to the 'checkout' stage where the users will enter their payment info.\n */\n const continueToCheckoutStage = async () => {\n const validationResult = await validate(\"stripe\");\n if (validationResult) {\n const addressValidationResult = await validateAddress();\n if (addressValidationResult) {\n trackingScripts.facebook(isProduction, FACEBOOK_EVENTS.CREDIT_CARD_PAYMENT, fbTrackingOrderObject, \"trackCustom\", shippingStageState.email);\n setPurchaseStage(PURCHASE_STAGES.PAYMENT);\n }\n }\n };\n\n // Grab the shipping prices for the selected country into some readable variable names.\n const { standard: standardShippingPrice, expedited: expeditedShippingPrice } = country.shippingPrices;\n\n /**\n * Upon a successful PayPal payment, the response will be a JSON representation of an order.\n * @param {Object} response\n */\n const onPayPalSuccess = (response) => {\n // When the customer has successfully made a purchase, remove them from Mailchimp as a potential customer.\n if (purchaseFlow === PURCHASE_FLOWS.SHAPA_END_USER) {\n mailChimpApi.updateUserTag(shippingStageState.email, MAIL_CHIMP_LIST_IDS.SHAPA_SUBSCRIBERS_LIST, MAIL_CHIMP_TAGS.ABANDONED_CART_TAG, MAIL_CHIMP_TAGS_STATUS.INACTIVE);\n }\n\n trackingScripts.facebook(isProduction, FACEBOOK_EVENTS.PAYPAL_PAYMENT, fbTrackingOrderObject, \"trackCustom\", shippingStageState.email);\n setOrder(response);\n setPurchaseStage(PURCHASE_STAGES.CONFIRMATION);\n };\n\n /**\n * Called when an error occurs during the PayPal checkout process.\n * @param {String} errorMessage\n */\n const onPayPalError = errorMessage => {\n setPaypalErrorMessage(`There was an error submitting your PayPal order: ${errorMessage}`);\n };\n\n const onPayPalValidation = async () => {\n const validationResult = await validate(\"paypal\");\n if (validationResult) {\n return await validateAddress();\n }\n return validationResult;\n };\n\n const giftCheckboxHandle = (event) => {\n const giftInitialData = { name: \"\", email: \"\", message: \"\", giftConfirmationEmail: recipientType.me, isGift: false };\n const giftStateUpdate = { ...giftSenderInformation, isGift: event.target.checked };\n if (event.target.checked) {\n setGiftSenderInformation(giftStateUpdate);\n } else {\n setGiftSenderInformation(giftInitialData);\n }\n\n /**\n * Hide validator messages when the user selects the `This is a gift` option.\n * Current validator messages may or may not be applicable.\n */\n validator.current.hideMessages();\n };\n\n /**\n * The email field is shared between the shipping/billing information and account creation.\n * @returns {JSX.Element}\n */\n const emailField = () => (\n \n {\n setShippingStageState({...shippingStageState, email: e.target.value})\n }}\n onBlur={() => handleEmailValidation(shippingStageState.email, setEmailDomainValidationError, () => {\n if (purchaseFlow === PURCHASE_FLOWS.SHAPA_END_USER) {\n mailChimpApi.saveEmailAddressToList(shippingStageState.email, MAIL_CHIMP_LIST_IDS.SHAPA_SUBSCRIBERS_LIST, MAIL_CHIMP_TAGS.ABANDONED_CART_TAG)\n }\n })}\n \n />\n {validator.current.message('email', shippingStageState.email, 'required|email')}\n {emailDomainValidationError && {emailDomainValidationError}
}\n \n );\n\n /**\n * The phone field is shared between the shipping/billing information and account creation.\n * @param {Boolean} [required = false]\n * @returns {JSX.Element}\n */\n const phoneField = (required = false) => (\n \n {\n setShippingStageState({...shippingStageState, phone: e.target.value})\n }}\n />\n {required &&\n \n {validator.current.message('phone', shippingStageState.phone, 'required|phone')}\n \n }\n \n );\n\n const npiField = () => (\n \n {\n setNpi(e.target.value)\n }}\n minlength={10}\n maxlength={10}\n />\n {validator.current.message('npi', shippingStageState.npi, 'numeric|min:10|max:10')}\n \n );\n\n // Since some fields are conditional, we need to purge the validator fields every render to make sure they are up-to-date for the current state.\n // (Ex. state is conditional depending on the selected country, gift validations depending on whether the user is purchasing as a gift).\n // See: https://github.com/dockwa/simple-react-validator#3-conditional-fields\n validator.current.purgeFields();\n\n return (\n \n {/* It's possible a scale won't have a shipping option (ex. there is a delay so we don't offer expedited shipping). */}\n {selectedDevice && selectedDevice.shippingOptionsAvailable &&\n
\n
Shipping method
\n
\n
setShippingStageState({...shippingStageState, shippingMethod: shippingMethods.standard})}\n />\n
\n
\n Regular shipping
\n ({shippingMethods.standard.timeFrame})
\n \n
{`$${standardShippingPrice.toFixed(2)}${quantitiesEnabled ? \"/item\" : \"\"}`}
\n
\n
\n {\n // Expedited shipping is only available for domestic orders.\n country.code === 'US' &&\n
\n
setShippingStageState({\n ...shippingStageState,\n shippingMethod: shippingMethods.expedited\n })}\n />\n
\n
\n Expedited shipping
\n ({shippingMethods.expedited.timeFrame})
\n \n
{`$${expeditedShippingPrice.toFixed(2)}${quantitiesEnabled ? \"/item\" : \"\"}`}
\n
\n
\n }\n
\n }\n\n
\n {/* If the user is not purchasing a scale, we'll use their shipping information as billing information. */}\n
{deviceLineItem ? 'Shipping information' : 'Billing information'}
\n\n {giftingEnabled &&
\n \n \n This is a gift\n \n
\n }\n\n
\n
\n\n
\n {errorMessage &&\n
\n {errorMessage}\n
\n }\n\n {paypalErrorMessage && !errorMessage &&\n
\n {paypalErrorMessage}\n
\n }\n
\n\n
\n {/* At this time, we don't support PayPal for orders where the quantity is > 1 */}\n {isPayPalSupported(lineItems) &&\n
\n \n
\n }\n
\n
\n
\n
\n );\n};\n\nShippingStage.propTypes = {\n countries: CountriesPropTypes.isRequired\n};\n\nexport default ShippingStage;\n","import { camelizeKeys } from 'humps';\nimport {PRODUCT_CATEGORIES} from \"../constants\";\n\nexport default {\n createOrder(portal, purchaseFlow, shippingInformation, paymentInformation, lineItems, promoCode, clientTotalPrice, referralCode, giftSenderInformation, adminReferralTargetCode, npi, deferredUserCreate) {\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\n const selectedPlan = planLineItem ? planLineItem.product : null;\n\n const order = {\n customer: {\n first_name: shippingInformation.firstName,\n last_name: shippingInformation.lastName,\n email: shippingInformation.email,\n address1: shippingInformation.address1,\n address2: shippingInformation.address2,\n phone: shippingInformation.phone,\n city: shippingInformation.city,\n state: shippingInformation.state,\n country_code: shippingInformation.country.code,\n\n using_billing_address: paymentInformation.isUsingBillingAddress,\n billing_address1: paymentInformation.paymentInputAddress,\n billing_address2: paymentInformation.paymentInputAddress2,\n billing_city: paymentInformation.paymentInputCity,\n billing_state: paymentInformation.paymentInputState,\n billing_zip: paymentInformation.paymentInputZip,\n billing_country_code: paymentInformation.paymentInputCountry.code\n },\n\n cc_name: paymentInformation.nameOnCard,\n cc_number: paymentInformation.creditCardNumber,\n cc_exp_date: paymentInformation.creditCardDate,\n cc_cvc: paymentInformation.cvv,\n cc_zip: shippingInformation.zipCode,\n referrer: localStorage.getItem(\"shapa_referrer\"),\n promo_code_text: promoCode ? promoCode.promoCodeText : \"\",\n referral_code_text: referralCode,\n admin_referral_target_code: adminReferralTargetCode,\n is_arrival_phone_call_requested: shippingInformation.isArrivalPhoneCallRequested,\n\n line_items: lineItems,\n\n shipping_type: shippingInformation.shippingMethod.type.toUpperCase(),\n\n subscription_name: selectedPlan ? selectedPlan.name : null,\n\n // So the server can compare its calculated price to the client's.\n client_total_price: clientTotalPrice,\n\n // This is how we know where the user is purchasing from (ex. SHAPA, CLEARSTEP, etc).\n portal: portal,\n\n // Gives us an even more specific indicator of the current purchase flow than just the portal.\n purchase_flow: purchaseFlow,\n\n // Only applicable for clinicians that enter an NPI value (registered clinician ID).\n npi,\n\n // If true, it means someone is purchasing on behalf of someone else (ex. organization owner), who will be sent an email and create an account later.\n deferred_user_create: deferredUserCreate,\n\n gift_sender_information: giftSenderInformation\n };\n\n return new Promise((resolve, reject) => {\n $.ajax({\n url: \"/api/v1/stripe/create_order\",\n type: \"POST\",\n data: {\n order: order,\n\n // Only applicable for a flow that requires account creation.\n user: {\n first_name: shippingInformation.firstName,\n last_name: shippingInformation.lastName,\n email: shippingInformation.email,\n phone: shippingInformation.phone,\n password: shippingInformation.password,\n password_confirmation: shippingInformation.passwordConfirmation,\n organization_name: shippingInformation.organizationName,\n organization_code: shippingInformation.organizationCode\n }\n },\n success: response => resolve(camelizeKeys(response)),\n error: response => reject(response)\n });\n });\n }\n}\n","import React, {useContext} from \"react\";\n\nimport { buyNowContext } from \"../BuyNow\";\nimport { calcShippingPrice } from \"../../utils/pricingHelper\";\n\n/**\n * Return a string representing the shipping price of the order.\n */\nconst ShippingPrice = () => {\n const { lineItems, shippingStageState } = useContext(buyNowContext);\n const { country, shippingMethod } = shippingStageState;\n\n return calcShippingPrice(lineItems, country, shippingMethod).toFixed(2);\n};\n\nexport default ShippingPrice;","import React, {useContext} from \"react\";\n\nimport { buyNowContext } from \"../BuyNow\";\nimport { calcSalesTax } from \"../../utils/pricingHelper\";\n\n/**\n * Return a string representing the total price of the order.\n */\nconst SalesTaxPrice = () => {\n const { lineItems, promoCode, salesTaxRates, shippingStageState } = useContext(buyNowContext);\n const { state, country } = shippingStageState;\n\n return calcSalesTax(lineItems, state, country, salesTaxRates, promoCode).toFixed(2);\n};\n\nexport default SalesTaxPrice;","import React, {useContext} from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport {buyNowContext} from \"../BuyNow\";\r\n\r\nconst LineItemDisplay = ({ lineItem, textClass }) => {\r\n const { quantitiesEnabled } = useContext(buyNowContext);\r\n let description;\r\n const originalUnitPrice = lineItem.product.price;\r\n const discountedUnitPrice = lineItem.unitPrice;\r\n\r\n const formatPrice = price => {\r\n const numericPrice = Number(price);\r\n const roundedPrice = Math.round(numericPrice);\r\n\r\n // The price is something like 99.0\r\n if (numericPrice === roundedPrice)\r\n return roundedPrice;\r\n\r\n // The price is something like 4.99\r\n return numericPrice;\r\n };\r\n\r\n // We want to show a different description of the line item depending on whether quantities are enabled.\r\n // If enabled, we want to show some individual product pricing information.\r\n // If not enabled, we want to show a text description.\r\n if (quantitiesEnabled) {\r\n if (originalUnitPrice - discountedUnitPrice > 1) {\r\n description = \r\n {`$${formatPrice(originalUnitPrice)}`} \r\n {`$${formatPrice(discountedUnitPrice)}`} \r\n
;\r\n }\r\n else\r\n description = {`$${formatPrice(originalUnitPrice)}`}
;\r\n }\r\n else {\r\n description = {lineItem.product.lineItemDescription}
;\r\n }\r\n\r\n return (\r\n \r\n
\r\n
{lineItem.product.lineItemName}
\r\n {description}\r\n
\r\n
\r\n {quantitiesEnabled &&
{lineItem.quantity}
}\r\n
\r\n
\r\n
${lineItem.price.toFixed(2)}
\r\n
\r\n
\r\n );\r\n};\r\n\r\nLineItemDisplay.defaultProps = {\r\n textClass: \"\"\r\n};\r\n\r\nLineItemDisplay.propTypes = {\r\n lineItem: PropTypes.object.isRequired,\r\n textClass: PropTypes.string\r\n};\r\n\r\nexport default LineItemDisplay;","import React, {useContext, useEffect, useState} from 'react';\nimport ProductPreview from \"../product_selection/ProductPreview\";\nimport { buyNowContext } from \"../BuyNow\";\nimport stripeApi from \"../../../../api/stripeApi\";\nimport ShippingPrice from \"../common/ShippingPrice\";\nimport SalesTaxPrice from \"../common/SalesTaxPrice\";\nimport TotalPrice from \"../common/TotalPrice\";\nimport PromoCode from \"../common/PromoCode\";\nimport mailChimpApi, { MAIL_CHIMP_LIST_IDS, MAIL_CHIMP_TAGS, MAIL_CHIMP_TAGS_STATUS } from \"../../../../api/mailChimpApi\";\nimport {calcTotalPrice} from \"../../utils/pricingHelper\";\nimport ReferralCode from \"../common/ReferralCode\";\nimport {formattedNextChargeDate} from \"../../utils/orderHelper\";\nimport trackingScripts from \"../../utils/trackingScripts\";\nimport { FACEBOOK_EVENTS } from \"../../utils/facebookHelper\";\nimport {PRODUCT_CATEGORIES, PURCHASE_FLOWS} from \"../../../../constants\";\nimport ProductPrice from \"../common/ProductPrice\";\nimport LineItemDisplay from \"../common/LineItemDisplay\";\n\nconst ReviewStage = () => {\n const {\n PURCHASE_STAGES, setOrder, lineItems, setPurchaseStage, shippingStageState,\n paymentStageState, promoCode, salesTaxRates, referralCode, giftSenderInformation, isProduction,\n portal, scaleProducts, quantitiesEnabled, purchaseFlow, adminReferralTargetCode, npi, deferredUserCreate\n } = useContext(buyNowContext);\n\n const scaleLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.SCALE);\n const adminAccessLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.ADMIN_ACCESS);\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\n\n const { country, state, shippingMethod } = shippingStageState;\n const [errorMessage, setErrorMessage] = useState(null);\n const [isProcessing, setIsProcessing] = useState(false);\n useEffect(() => {\n trackingScripts.hotjar(isProduction, \"/orders/buy-now/review_stage\");\n trackingScripts.facebook(\n isProduction,\n FACEBOOK_EVENTS.REACT_PAGE_VIEW,\n { page: \"ReviewStage\" },\n \"trackCustom\"\n );\n }, []);\n\n const getLastFourCharOfString = (string) => {\n return string.slice(string.length - 4);\n };\n\n const createStripeOrder = () => {\n if (isProcessing)\n return;\n\n setIsProcessing(true);\n\n // Reset the error message while we attempt to create the order.\n setErrorMessage(null);\n\n // Calculate the total price of the order so the client's value can be compared to the server's value.\n const totalPrice = calcTotalPrice(lineItems, country, state, shippingMethod, salesTaxRates, promoCode);\n\n stripeApi.createOrder(portal, purchaseFlow, shippingStageState, paymentStageState, lineItems, promoCode, totalPrice, referralCode, giftSenderInformation, adminReferralTargetCode, npi, deferredUserCreate)\n .then(response => {\n if (response.status === 'PROCESSED') {\n if (response.confirmationToken != null) {\n // When the customer has successfully made a purchase, remove them from Mailchimp as a potential customer.\n if (purchaseFlow === PURCHASE_FLOWS.SHAPA_END_USER) {\n mailChimpApi.updateUserTag(shippingStageState.email, MAIL_CHIMP_LIST_IDS.SHAPA_SUBSCRIBERS_LIST, MAIL_CHIMP_TAGS.ABANDONED_CART_TAG, MAIL_CHIMP_TAGS_STATUS.INACTIVE);\n }\n\n setOrder(response);\n setPurchaseStage(PURCHASE_STAGES.CONFIRMATION);\n } else {\n setErrorMessage('We were unable to process your order. Please contact Shapa customer support.');\n }\n } else if (response.status === 'DECLINED') {\n setErrorMessage('It seems your payment was declined. Please ensure your payment information is correct. We apologize for the inconvenience.');\n } else {\n // Fail-safe error message.\n setErrorMessage('We are currently unable to process your payment. Please try again later.');\n }\n })\n .catch(response => {\n setErrorMessage(response.responseText);\n })\n .finally(() => {\n setIsProcessing(false);\n });\n };\n\n // The primary text class used within the order review.\n const primaryTextClass = \"plan-main-txt fw-bolder text-dark mb-0 pb-0\";\n\n // Decide which product to show in the preview component.\n let previewProduct;\n if (scaleLineItem)\n previewProduct = scaleLineItem.product;\n else if (adminAccessLineItem)\n previewProduct = adminAccessLineItem.product;\n else\n previewProduct = scaleProducts[0];\n\n return (\n \n Review your order
\n\n \n\n \n {lineItems.map(lineItem =>
)}\n\n {scaleLineItem &&\n
\n
\n
\n {quantitiesEnabled &&
{scaleLineItem.quantity}
}\n
\n
\n
\n }\n\n
\n\n
\n
\n\n
\n
\n\n \n
\n
\n
Shipping information
\n
\n {scaleLineItem && shippingStageState.shippingMethod.label}\n {scaleLineItem && }\n {shippingStageState.firstName} {shippingStageState.lastName}\n \n {shippingStageState.address1}\n \n {shippingStageState.address2 && shippingStageState.address2}\n {shippingStageState.address2 && }\n {shippingStageState.city}, {shippingStageState.state} {shippingStageState.zipCode}\n
\n
\n
\n
setPurchaseStage(PURCHASE_STAGES.SHIPPING)}>Edit
\n
\n
\n
\n
\n
Payment information
\n
Visa ending in {getLastFourCharOfString(paymentStageState.creditCardNumber)}
\n
{paymentStageState.nameOnCard}
\n {\n paymentStageState.isUsingBillingAddress &&
\n {paymentStageState.paymentInputAddress}\n \n {paymentStageState.paymentInputAddress2 && paymentStageState.paymentInputAddress2}\n {paymentStageState.paymentInputAddress2 && }\n {paymentStageState.paymentInputCity}, {paymentStageState.paymentInputState} {paymentStageState.paymentInputZip}\n
\n }\n
\n
\n
setPurchaseStage(PURCHASE_STAGES.PAYMENT)}>\n Edit\n
\n
\n
\n\n {planLineItem && planLineItem.product.recurringPlan &&\n
\n
\n Your subscription will automatically renew unless you cancel\n before {formattedNextChargeDate(planLineItem.product, promoCode)}.\n Your cancellation will take effect at the end of your current billing cycle.\n
\n
\n }\n
\n\n \n \n \n );\n};\n\nexport default ReviewStage;","import React, { useContext } from \"react\";\nimport * as PropTypes from \"prop-types\";\nimport TotalPrice from \"../common/TotalPrice\";\nimport PromoCode from \"../common/PromoCode\";\nimport {buyNowContext} from \"../BuyNow\";\nimport ShippingPrice from \"../common/ShippingPrice\";\nimport SalesTaxPrice from \"../common/SalesTaxPrice\";\nimport ReferralCode from \"../common/ReferralCode\";\nimport {formattedNextChargeDate} from \"../../utils/orderHelper\";\nimport RecurringPlanPrice from \"../common/RecurringPlanPrice\";\nimport {PRODUCT_CATEGORIES} from \"../../../../constants\";\nimport ProductPrice from \"../common/ProductPrice\";\nimport LineItemDisplay from \"../common/LineItemDisplay\";\n\nconst OrderSummary = ({ showOrderSummary, showInMobileOnly }) => {\n const { lineItems, promoCode, shippingStageState, quantitiesEnabled } = useContext(buyNowContext);\n\n const scaleLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.SCALE);\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\n\n // The primary class used for text within the order summary.\n const primaryTextClass = \"plan-main-txt text-dark mb-0 pb-0 fw-bolder\";\n\n return (\n \n
\n \n
\n
Order summary
\n\n {lineItems.map(lineItem =>
)}\n\n {scaleLineItem &&\n
\n
\n
Shipping
\n
{shippingStageState.shippingMethod.timeFrame}
\n
\n
\n {quantitiesEnabled &&
{scaleLineItem.quantity}
}\n
\n
\n
\n }\n\n
\n\n
\n
\n\n
\n
\n\n {planLineItem && planLineItem.product.recurringPlan &&\n
\n
\n {`Your subscription will automatically renew unless you cancel before ${formattedNextChargeDate(planLineItem.product, promoCode)}. Your cancellation will take effect at the end of your current billing cycle.`}\n
\n
\n }\n\n
\n \n )\n};\n\nOrderSummary.propTypes = {\n showOrderSummary: PropTypes.bool.isRequired,\n showInMobileOnly: PropTypes.bool\n};\n\nOrderSummary.defaultProps = {\n showInMobileOnly: false\n}\n\nexport default OrderSummary;","import React, {useState, useEffect, useContext} from 'react';\n\nimport {buyNowContext} from \"../BuyNow\";\nimport CheckoutHeader from \"./CheckoutHeader\";\nimport PaymentStage from \"../payment/PaymentStage\";\nimport ShippingStage from \"../shipping/ShippingStage\";\nimport ReviewStage from \"../order_review/ReviewStage\";\nimport OrderSummary from \"../order_summary/OrderSummary\";\n\nimport CountriesPropTypes from \"../../prop-types/CountriesPropTypes\";\n\nconst Checkout = ({countries}) => {\n const { PURCHASE_STAGES, purchaseStage } = useContext(buyNowContext);\n const [showOrderSummary, setShowOrderSummary] = useState(false);\n\n return (\n \n \n \n
\n {\n purchaseStage === PURCHASE_STAGES.SHIPPING &&
\n }\n {\n purchaseStage === PURCHASE_STAGES.PAYMENT && \n }\n {\n purchaseStage === PURCHASE_STAGES.REVIEW && \n }\n \n \n
\n \n );\n};\n\nCheckout.propTypes = {\n countries: CountriesPropTypes.isRequired\n};\n\nexport default (props) => ;","import React, {useContext} from \"react\";\nimport { disableBodyScroll, enableBodyScroll } from \"body-scroll-lock\";\n\nimport { buyNowContext } from \"../BuyNow\";\nimport HeaderCompanyLogo from \"../../../common/HeaderCompanyLogo\";\nimport { useHideHeaderOnMobile, useViewport } from \"../../utils/custom_hooks\";\n\nconst ConfirmationHeader = ({ displayOrderDetailsDropdown, showOrderDetails, setShowOrderDetails }) => {\n const { width } = useViewport();\n const { portal } = useContext(buyNowContext);\n useHideHeaderOnMobile(\"confirmation-header\", width, 700);\n\n const onClickShowOrderDetails = () => {\n const summaryDropMenu = document.querySelector('#summary');\n setShowOrderDetails(!showOrderDetails);\n showOrderDetails ? enableBodyScroll(summaryDropMenu): disableBodyScroll(summaryDropMenu);\n };\n\n return (\n \n )\n};\n\nexport default ConfirmationHeader","import _ from \"lodash\";\nimport clsx from \"clsx\";\nimport React, { useContext } from 'react';\nimport * as PropTypes from 'prop-types';\nimport { buyNowContext } from \"../BuyNow\";\nimport TotalPrice from \"./TotalPrice\";\nimport {formattedNextChargeDate} from \"../../utils/orderHelper\";\nimport {PORTALS, PRODUCT_CATEGORIES} from \"../../../../constants\";\n\nconst OrderDetails = ({ showOrderDetails }) => {\n const { lineItems, shippingStageState, paymentStageState, order, promoCode, portal, quantitiesEnabled } = useContext(buyNowContext);\n\n const scaleLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.SCALE);\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\n const selectedScale = scaleLineItem ? scaleLineItem.product : null;\n const selectedPlan = planLineItem ? planLineItem.product : null;\n\n const getLastFourCharOfString = (string) => {\n return string.slice(string.length - 4)\n };\n\n const scaleExists = !_.isEmpty(selectedScale);\n\n let licenseKeyMessage = \"\";\n\n if (planLineItem) {\n if (planLineItem.quantity > 1)\n licenseKeyMessage = `Each license key will be linked to a plan you just purchased. Each license key supports a maximum of ${selectedPlan.allowableUsers} account(s).`;\n else\n licenseKeyMessage = `This license key is linked to the plan you just purchased. The license key supports a maximum of ${selectedPlan.allowableUsers} account(s).`;\n }\n\n let paymentMethodMessage = \"\";\n if (paymentStageState.creditCardNumber)\n paymentMethodMessage = `Visa ending in ${getLastFourCharOfString(paymentStageState.creditCardNumber)}`;\n else\n paymentMethodMessage = \"Payment via PayPal\";\n\n /**\n * Produce UI to display the name of a line item.\n * @param {LineItem} lineItem\n */\n const lineItemName = lineItem => {\n return {lineItem.product.lineItemName}
;\n };\n\n /**\n * Produce UI to display the description of a line item.\n * @param {LineItem} lineItem\n */\n const lineItemDescription = lineItem => {\n let desc = lineItem.product.lineItemDescription;\n\n // If quantities are enabled, we need to display the quantity of the product rather than a description.\n if (quantitiesEnabled)\n desc = `Qty: ${lineItem.quantity}`;\n\n return {desc}
;\n };\n\n return (\n \n
\n \n
\n
\n
Order details
\n
\n
\n
Confirmation number
\n\n {lineItems.map(lineItem => lineItemName(lineItem))}\n
\n
\n
{order.confirmationToken}
\n\n {lineItems.map(lineItem => lineItemDescription(lineItem))}\n
\n
\n\n {selectedPlan &&\n
\n
\n
{`Unique license ${planLineItem.quantity > 1 ? 'keys' : 'key'}`}
\n
{licenseKeyMessage}
\n
\n
\n {\n order.licenseKeys.map(licenseKey => (\n
{licenseKey.key}
\n ))\n }\n
\n
\n }\n\n {scaleExists &&\n
\n
\n
Shipping
\n
\n {shippingStageState.firstName} {shippingStageState.lastName}\n \n {shippingStageState.address1}\n \n {shippingStageState.city}, {shippingStageState.state} {shippingStageState.zipCode}\n
\n
\n {/* Don't show an estimated shipping timeframe if shipping options aren't available for the selected scale. We might not have a good estimate in this case. */}\n {selectedScale && selectedScale.shippingOptionsAvailable &&\n
\n
{shippingStageState.shippingMethod.timeFrame}
\n
\n }\n
\n }\n\n
\n
\n
Payment total
\n
{paymentMethodMessage}
\n
{paymentStageState.nameOnCard}
\n
\n
\n
\n
\n\n {selectedPlan && selectedPlan.recurringPlan &&\n
\n
\n Your subscription will automatically renew unless you cancel before {formattedNextChargeDate(selectedPlan, promoCode)}. Your cancellation will take effect at the end of your current billing cycle.\n
\n
\n }\n
\n
\n \n )\n};\n\nOrderDetails.protoTypes = {\n showOrderDetails: PropTypes.bool.isRequired,\n};\n\nexport default OrderDetails;","import { decamelizeKeys } from \"humps\";\nimport ReactOnRails from \"react-on-rails\";\n\nexport default {\n claimOwnership(userEmail, orderConfirmationToken) {\n return new Promise((resolve, reject) => {\n $.ajax({\n url: \"/api/v1/orders/claim_ownership\",\n type: \"POST\",\n data: decamelizeKeys({ userEmail, orderConfirmationToken }),\n success: response => resolve(response),\n error: response => reject(response)\n });\n });\n },\n\n sendGiftEmail(confirmationNumber) {\n // Since we are making a request to a non-API JSON action, we need a CSRF token.\n const authenticityToken = ReactOnRails.authenticityToken();\n\n return new Promise((resolve, reject) => {\n $.ajax({\n url: \"/orders/send_gift_email_only\",\n type: \"POST\",\n data: decamelizeKeys({ confirmationNumber, authenticityToken }),\n success: response => resolve(response),\n error: response => reject(response.responseText)\n });\n });\n }\n}","import React, {Fragment, useState} from \"react\";\nimport ordersApi from \"../../../../api/ordersApi\";\n\n/**\n * Displays a text box for the purchaser to enter their email if they have an existing account.\n * Once submitted, the user will claim ownership of the order with the given confirmation number.\n * @param {string} orderConfirmationToken\n * @returns {JSX.Element}\n * @constructor\n */\nfunction LinkOrderToUser({ orderConfirmationToken }) {\n const [userEmail, setUserEmail] = useState(null);\n const [errorMessage, setErrorMessage] = useState(null);\n const [isLinked, setIsLinked] = useState(false);\n\n async function submit() {\n setErrorMessage(null);\n\n try {\n await ordersApi.claimOwnership(userEmail, orderConfirmationToken);\n setIsLinked(true);\n }\n catch (ex) {\n if ((ex.status === 403 || ex.status === 404) && ex.responseJSON.error)\n setErrorMessage(ex.responseJSON.error);\n else\n setErrorMessage(\"An error has occurred while attempting to link your order. Please contact support if you continue to have issues.\");\n }\n }\n\n return (\n \n {!isLinked &&\n
\n \n If you have an existing account, enter your email address to link this order to your account now!\n
\n\n { setUserEmail(e.target.value); }}\n />\n\n \n Link now!\n \n \n }\n\n {isLinked &&\n
\n Success! This order has been linked to your account.\n
\n }\n\n {errorMessage &&\n
\n {errorMessage}\n
\n }\n
\n );\n}\n\nexport default LinkOrderToUser;","import React, {Fragment, useContext} from \"react\";\r\nimport {buyNowContext} from \"../BuyNow\";\r\nimport {PORTALS, PRODUCT_CATEGORIES, PURCHASE_FLOWS} from \"../../../../constants\";\r\nimport LinkOrderToUser from \"./LinkOrderToUser\";\r\n\r\nconst ConfirmationMessage = () => {\r\n const { lineItems, shippingStageState, portal, purchaseFlow, order, deferredUserCreate } = useContext(buyNowContext);\r\n\r\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\r\n const adminAccessLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.ADMIN_ACCESS);\r\n const scaleLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.SCALE);\r\n const scaleExists = !!scaleLineItem;\r\n const adminAccessExists = !!adminAccessLineItem;\r\n const planExists = !!planLineItem;\r\n\r\n const shapaMessage = (\r\n \r\n
Thank you for your order
\r\n\r\n
\r\n We've emailed you the order confirmation details.\r\n
\r\n\r\n {scaleExists &&\r\n
\r\n {`Your order will arrive within ${shippingStageState.shippingMethod.timeFrame}.`}\r\n
\r\n }\r\n\r\n {purchaseFlow !== PURCHASE_FLOWS.SHAPA_BP_ADDON && purchaseFlow !== PURCHASE_FLOWS.CLEARSTEP_BP_ADDON &&\r\n
\r\n In order to use Shapa, you’ll need an account. Start personalizing your program while waiting for your Shapa!\r\n
\r\n }\r\n
\r\n );\r\n\r\n const generalMessage = (\r\n \r\n
Thank you for your order
\r\n\r\n {/* If an existing clinician made a membership-only purchase (ex. site license), let's ask to link to their existing account. */}\r\n {!adminAccessExists && planExists &&\r\n (\r\n purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_CLINICIAN ||\r\n purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_CLINICIAN\r\n ) &&\r\n
\r\n \r\n \r\n \r\n }\r\n\r\n
\r\n We've emailed you the order confirmation details.\r\n
\r\n\r\n {scaleExists &&\r\n
\r\n {`Your order will arrive within ${shippingStageState.shippingMethod.timeFrame}.`}\r\n
\r\n }\r\n
\r\n );\r\n\r\n const generalMessageBehalfOrgOwner = (\r\n \r\n
Thank you for your order!
\r\n
Important information has been emailed to {shippingStageState.email}.
\r\n
\r\n They have been emailed order confirmation details, and an email with a link to proceed with account creation.\r\n
\r\n
\r\n );\r\n\r\n if (portal === PORTALS.SHAPA)\r\n return shapaMessage;\r\n else {\r\n if (deferredUserCreate)\r\n return generalMessageBehalfOrgOwner;\r\n\r\n return generalMessage;\r\n }\r\n};\r\n\r\nexport default ConfirmationMessage;","\r\nimport React, {useContext, useState} from \"react\";\r\nimport usersApi from \"../../../../api/usersApi\";\r\nimport {buyNowContext} from \"../BuyNow\";\r\nimport PropTypes from \"prop-types\";\r\nimport { useSimpleReactValidator } from \"../../utils/useSimpleReactValidator\";\r\nimport {SIGNUP_USER_TYPES} from \"../../../../constants\";\r\n\r\nconst CreateAccountForm = ({ onAccountCreated, appName, signupUserType }) => {\r\n const validator = useSimpleReactValidator();\r\n const { shippingStageState, order, portal } = useContext(buyNowContext);\r\n\r\n const [errorMessage, setErrorMessage] = useState(null);\r\n const [isProcessing, setIsProcessing] = useState(false);\r\n const [licenseKeyText, setLicenseKeyText] = useState(order.licenseKeys[0].key);\r\n const [newUserData, setNewUserData] = useState({\r\n firstName: shippingStageState.firstName,\r\n lastName: shippingStageState.lastName,\r\n email: shippingStageState.email,\r\n phone: shippingStageState.phone,\r\n password: \"\",\r\n passwordConfirmation: \"\",\r\n signupUserType\r\n });\r\n\r\n /**\r\n * A helper method to set a field of the newUserData object.\r\n * @param {Object} e - Event from an input element.\r\n * @param {String} fieldName - Name of the field to update within newUserData.\r\n */\r\n const updateNewUserField = (e, fieldName) => {\r\n const updateObj = {};\r\n updateObj[fieldName] = e.target.value;\r\n setNewUserData({ ...newUserData, ...updateObj })\r\n };\r\n\r\n /**\r\n * Make an API request to create a user from the given state of this component.\r\n */\r\n const createUser = () => {\r\n if (validator.current.allValid()) {\r\n validator.current.hideMessages();\r\n } else {\r\n validator.current.showMessages();\r\n return;\r\n }\r\n\r\n if (isProcessing) {\r\n return;\r\n }\r\n\r\n setIsProcessing(true);\r\n\r\n // Reset the error message while we attempt to create the user.\r\n setErrorMessage(null);\r\n\r\n usersApi.createUser(newUserData, licenseKeyText, portal)\r\n .then(user => {\r\n // Although the user was created, they have not been logged into the website. Do this now.\r\n // Note that we are returning the user in the promise chain, not the response to webLogin().\r\n return usersApi.webLogin(user.email, newUserData.password).then(() => user);\r\n })\r\n .then(user => {\r\n onAccountCreated(user);\r\n })\r\n .catch(responseErrorMessage => {\r\n setErrorMessage(responseErrorMessage);\r\n })\r\n .finally(() => {\r\n setIsProcessing(false);\r\n });\r\n };\r\n\r\n return (\r\n \r\n );\r\n};\r\n\r\nCreateAccountForm.defaultProps = {\r\n onAccountCreated: () => {},\r\n signupUserType: SIGNUP_USER_TYPES.DEFAULT\r\n};\r\n\r\nCreateAccountForm.propTypes = {\r\n onAccountCreated: PropTypes.func,\r\n signupUserType: PropTypes.string,\r\n appName: PropTypes.string.isRequired\r\n}\r\n\r\nexport default CreateAccountForm;","import React, {useState} from \"react\";\r\nimport CreateAccountForm from \"./CreateAccountForm\";\r\nimport PropTypes from \"prop-types\";\r\nimport {SIGNUP_USER_TYPES} from \"../../../../constants\";\r\n\r\nconst CreateClearStepClientAccount = ({ onAccountCreated, appName }) => {\r\n const [signupUserType, setSignupUserType] = useState(null);\r\n\r\n const SignupUserTypeChoice = ({ caption, type }) => (\r\n setSignupUserType(type)}\r\n >\r\n {caption}\r\n \r\n );\r\n\r\n return (\r\n \r\n \r\n
\r\n Will you be using MyClearStep for yourself or\r\n \r\n for your child/dependent?\r\n
\r\n\r\n
\r\n
\r\n\r\n {signupUserType === SIGNUP_USER_TYPES.PARENT &&\r\n
\r\n Create an account below to get started.You will use the same email and password to access the measurement data and manage your billing. \r\n
\r\n }\r\n\r\n {signupUserType === SIGNUP_USER_TYPES.DEFAULT &&\r\n
\r\n Create an account below to get started.You will use the same email and password to log in to the MyClearStep app. \r\n
\r\n }\r\n
\r\n\r\n {signupUserType && }\r\n \r\n );\r\n};\r\n\r\nCreateClearStepClientAccount.defaultProps = {\r\n onAccountCreated: () => {}\r\n};\r\n\r\nCreateClearStepClientAccount.propTypes = {\r\n onAccountCreated: PropTypes.func,\r\n appName: PropTypes.string.isRequired\r\n}\r\n\r\nexport default CreateClearStepClientAccount;","import React, { useContext, useEffect, useState } from \"react\";\nimport ConfirmationHeader from \"./ConfirmationHeader\";\nimport OrderDetails from \"../common/OrderDetails\";\n\nimport { buyNowContext } from \"../BuyNow\";\nimport trackingScripts from \"../../utils/trackingScripts\";\nimport { createOrderObject, FACEBOOK_EVENTS } from \"../../utils/facebookHelper\";\nimport {PORTALS, PRODUCT_CATEGORIES, PURCHASE_FLOWS, SIGNUP_USER_TYPES} from \"../../../../constants\";\nimport ConfirmationMessage from \"./ConfirmationMessage\";\nimport CreateAccountForm from \"./CreateAccountForm\";\nimport CreateClearStepClientAccount from \"./CreateClearStepClientAccount\";\n\nconst Confirmation = () => {\n const {\n PURCHASE_STAGES, shippingStageState, order, lineItems, isProduction, giftSenderInformation,\n portal, purchaseFlow, appInfo\n } = useContext(buyNowContext);\n const businessName = appInfo.name;\n const URLS = {\n \"Shapa\": '/home',\n \"MyClearStep\": 'https://www.myclearstep.com',\n };\n\n const scaleLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.SCALE);\n const planLineItem = lineItems.find(li => li.product.category === PRODUCT_CATEGORIES.MEMBERSHIP);\n const selectedScale = scaleLineItem ? scaleLineItem.product : null;\n const selectedPlan = planLineItem ? planLineItem.product : null;\n\n const [user, setUser] = useState(null);\n const [confirmationStage, setConfirmationStage] = useState(1);\n const [showOrderDetails, setShowOrderDetails] = useState(false);\n\n const fbTrackingOrderObject = createOrderObject(selectedScale, selectedPlan);\n\n useEffect(() => {\n history.replaceState({ stage: PURCHASE_STAGES.CONFIRMATION }, null, \"\");\n\n // Remove the local storage item we used to keep track of how the user was referred to our site.\n localStorage.removeItem(\"shapa_referrer\");\n\n trackingScripts.googleECommerce(isProduction, order);\n trackingScripts.hotjar(isProduction, \"/orders/buy-now/purchase_confirmation\");\n trackingScripts.facebook(isProduction, FACEBOOK_EVENTS.REACT_PAGE_VIEW, { page: \"PurchaseConfirmation\" }, \"trackCustom\");\n trackingScripts.facebook(isProduction, FACEBOOK_EVENTS.PURCHASE, fbTrackingOrderObject, \"track\", shippingStageState.email);\n trackingScripts.googleConversionGTag(isProduction);\n trackingScripts.sendGoogleAnalyticsEvent(isProduction, \"purchase\", \"ecommerce\", \"Purchase\");\n }, []);\n\n useEffect(() => {\n if (giftSenderInformation.isGift) {\n setConfirmationStage(2);\n }\n }, []);\n\n const scrollToCreateAccount = () => {\n document.getElementById('create-from').scrollIntoView({behavior: \"smooth\", block: \"center\", inline: \"nearest\"});\n };\n\n const postOrderPurchase = () => (\n \n
\n
\n
\n
\n \n
\n {confirmationStage === 1 &&
}\n {confirmationStage === 2 &&
\n
Thank you for your order
\n
We've emailed you order confirmation details. Your Shapa will arrive within 5-7 days.
\n
\n We have also sent a gift confirmation email that includes important steps to get started and a unique license key that your recipient will need to create an account.\n
\n
}\n {confirmationStage === 2 &&
}\n
\n {confirmationStage === 1 &&\n
\n {/* The various purchase flows have a slightly different account creation flow. */}\n\n {(purchaseFlow === PURCHASE_FLOWS.SHAPA_END_USER || purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_END_USER) &&\n \n {/* This button will scroll down to the create form when clicked. */}\n \n setUser(userResponse)} appName={businessName}/>\n \n }\n {(purchaseFlow === PURCHASE_FLOWS.SHAPA_BP_ADDON || purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_BP_ADDON) && (\n \n )}\n {(purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_END_USER) && setUser(userResponse)} appName={businessName}/>}\n {(purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_CLINICIAN || purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_PARENT) &&\n \n }\n \n }\n
\n
\n
\n\n { // Share-a-Sale tracking pixel.\n isProduction &&\n
\n }\n
\n );\n\n const postAccountCreation = () => (\n \n
\n\n
You are now registered with {businessName} \n\n {/* Tell the user to download the app unless they have purchased for a child/dependent. */}\n {user.signupUserType !== SIGNUP_USER_TYPES.PARENT &&\n
\n Get started by downloading the {businessName} app!
\n\n \n \n }\n\n
\n
\n
Your account details \n\n
First Name: {user.firstName}
\n
Last Name: {user.lastName}
\n
Email: {user.email}
\n {user.phone &&
Phone: {user.phone}
}\n\n
\n
\n
\n
\n );\n\n return (\n \n {/*\n Need a better solution here. When an account has been created, we no longer render the OrderDetails component, so it won't be shown when the dropdown is clicked.\n The best solution is to use two OrderDetails components. One for the dropdown and one for the desktop view. Why are we only using one component?\n Another solution is to show the regular header and don't render the ConfirmationHeader once a user account is created.\n */}\n \n\n {/* We'll first show the order confirmation page. If the user optionally creates an account, we'll then show an account confirmation page. */}\n {user ? postAccountCreation() : postOrderPurchase()}\n \n );\n};\n\nexport default Confirmation;\n","import React, {useContext, useState, useEffect, Fragment} from \"react\";\nimport {buyNowContext} from \"../BuyNow\";\nimport {PORTALS} from \"../../../../constants\";\n\nfunction ChooseModeStage() {\n const {\n PURCHASE_STAGES, setPurchaseStage, appInfo, CLINICIAN_MODES, setClinicianMode, portal\n } = useContext(buyNowContext);\n\n const businessName = appInfo.name;\n\n return (\n \n
\n \n
\n\n
\n Welcome to MyClearStep! Please select an option to proceed.\n
\n\n
{\n setClinicianMode(CLINICIAN_MODES.NEW_ORGANIZATION);\n setPurchaseStage(PURCHASE_STAGES.PRODUCT_SELECTION);\n }}>\n I'm a new clinician that needs to purchase admin portal access and create an organization.\n \n\n
{\n setClinicianMode(CLINICIAN_MODES.EXISTING_ORGANIZATION);\n setPurchaseStage(PURCHASE_STAGES.PRODUCT_SELECTION);\n }}>\n I'm an existing clinician with an organization that needs to purchase additional admin access seats or other\n products.\n \n\n
{\n if (portal === PORTALS.CLEARSTEP)\n window.location = \"/organizations/join-mcs\";\n else\n window.location = \"/organizations/join\";\n }}>\n I need to join an existing organization.\n \n
\n );\n}\n\nfunction Option({ title, onClick = () => {}, children }) {\n return (\n \n
{title}
\n {children}\n\n
\n
\n\n
\n
\n );\n}\n\nexport default ChooseModeStage;","import React, {useState, createContext, useEffect} from 'react';\nimport * as PropTypes from 'prop-types';\n\nimport ProductSelection from \"./product_selection/ProductSelection\";\nimport Checkout from \"./checkout/Checkout\";\nimport Confirmation from \"./confirmation/Confirmation\";\n\nimport CountriesPropTypes from \"../prop-types/CountriesPropTypes\";\nimport {PORTALS, PRODUCT_CATEGORIES, PURCHASE_FLOWS} from \"../../../constants\";\nimport LineItem from \"../utils/LineItem\";\nimport ChooseModeStage from \"./choose_mode/ChooseModeStage\";\n\nexport const buyNowContext = createContext(null);\n\nexport const shippingMethods = {\n standard: { type: \"standard\", get label() { return `Regular (${this.timeFrame})`; }, timeFrame: \"5-7 business days\" },\n expedited: { type: \"expedited\", get label() { return `Expedited (${this.timeFrame})`; }, timeFrame: \"2-3 business days\" }\n};\n\nexport const recipientType = {\n me: { type: \"me\"}, recipient: { type: \"recipient\"}\n};\n\n/**\n * This component is a host for the 3 purchase stages (product selection, shipping info, and credit card info).\n */\nconst BuyNow = ({ portal, purchaseFlow, promotional, scaleBpBundleProducts, scaleProducts, bpDeviceProducts, planProducts, adminAccessProducts, countries, salesTaxRates, isProduction, referralCode, referralCodeValid, plansConfiguration, selectedPlanId, urlPromoCode, adminReferralTargetCode, abTestingPage }) => {\n // Some purchase flows may start with an empty order and the user must select which items they want to purchase.\n // Other purchase flows may start with items already selected, meaning the user can modify the selection or just proceed to the next page without making any changes.\n let initialLineItems = [];\n if (purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_END_USER || purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_PARENT || purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_END_USER) {\n initialLineItems = [\n new LineItem({ product: scaleBpBundleProducts[0], quantity: 1 }),\n new LineItem({ product: planProducts[0], quantity: 1 })\n ];\n\n if (purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_PARENT)\n initialLineItems.push(new LineItem({ product: adminAccessProducts[0], quantity: 1 }));\n } else if (purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_CLINICIAN || purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_CLINICIAN) {\n initialLineItems = [\n new LineItem({ product: adminAccessProducts[0], quantity: 1 })\n ];\n } else if (purchaseFlow === PURCHASE_FLOWS.SHAPA_END_USER) {\n let initialSelectedPlan = planProducts.find(p => p.default);\n\n // If there is a selected plan in the query string (ex. the user selected a plan on the homepage), use that instead of the default.\n if (selectedPlanId) {\n const queryStringSelectedPlanIndex = planProducts.findIndex((plan) => plan.id === parseInt(selectedPlanId));\n const queryStringSelectedPlan = planProducts[queryStringSelectedPlanIndex];\n if (queryStringSelectedPlan)\n initialSelectedPlan = queryStringSelectedPlan;\n }\n\n initialLineItems = [\n new LineItem({ product: scaleProducts[0], quantity: 1 }),\n new LineItem({ product: initialSelectedPlan, quantity: 1 }),\n ];\n } else if (purchaseFlow === PURCHASE_FLOWS.SHAPA_BP_ADDON || purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_BP_ADDON) {\n initialLineItems = [\n new LineItem({ product: bpDeviceProducts[0], quantity: 1 }),\n ];\n }\n\n const CLINICIAN_MODES = { NEW_ORGANIZATION: \"NEW_ORGANIZATION\", EXISTING_ORGANIZATION: \"EXISTING_ORGANIZATION\" };\n const PURCHASE_STAGES = { CHOOSE_MODE: \"CHOOSE_MODE\", PRODUCT_SELECTION: \"PRODUCT_SELECTION\", SHIPPING: \"SHIPPING\", PAYMENT: \"PAYMENT\", REVIEW: \"REVIEW\", CONFIRMATION: \"CONFIRMATION\" };\n const INITIAL_PURCHASE_STATE = purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_CLINICIAN || purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_CLINICIAN ? PURCHASE_STAGES.CHOOSE_MODE : PURCHASE_STAGES.PRODUCT_SELECTION;\n const [promoCode, setPromoCode] = useState(null);\n const [totalPrice, setTotalPrice] = useState(0);\n const [purchaseStage, setPurchaseStageDirect] = useState(INITIAL_PURCHASE_STATE);\n const [clinicianMode, setClinicianMode] = useState(CLINICIAN_MODES.NEW_ORGANIZATION);\n const [order, setOrder] = useState(null);\n const [lineItems, setLineItems] = useState(initialLineItems);\n const [npi, setNpi] = useState(null);\n const [deferredUserCreate, setDeferredUserCreate] = useState(false);\n\n const setPurchaseStage = val => {\n setPurchaseStageDirect(val);\n history.pushState({ stage: val }, null, \"\");\n\n // Each time we set the purchase stage, we'll scroll to the top of the window.\n // We've actually had people complain that sometimes they only see the footer and it has been impossible to reproduce for us, even with Guy going to one customer's house to use their phone and computer.\n // I have a suspicion that they have a small screen or window and sometimes get to a purchase stage that is scrolled too far from the previous stage and they don't know they have to scroll up.\n // So, this is very important code. Don't remove unless replacing with a better solution.\n window.scrollTo(0, 0);\n };\n\n /**\n * This method will take an array of line items and for each one, first search for an existing line item of the same product category (ex. device or membership) and device type (ex. scale or BP device)\n * and update the line item if found. If an existing line item isn't found, the given line item will simply be appended.\n * @param {LineItem[]} lineItemUpdates\n */\n const setLineItemsByCategory = lineItemUpdates => {\n const newLineItems = [...lineItems];\n\n lineItemUpdates.forEach(lineItemUpdate => {\n const lineItemObj = new LineItem(lineItemUpdate);\n\n const existingLineItemIndex = newLineItems.findIndex(li => li.product.category === lineItemObj.product.category);\n if (existingLineItemIndex >= 0)\n newLineItems[existingLineItemIndex] = lineItemObj;\n else\n newLineItems.push(lineItemObj);\n });\n\n // Sort line items such that memberships appear after any other products such as scales, BP devices, and admin access.\n newLineItems.sort((a, b) => {\n const aOrder = a.product.category === PRODUCT_CATEGORIES.MEMBERSHIP ? 1 : 0;\n const bOrder = b.product.category === PRODUCT_CATEGORIES.MEMBERSHIP ? 1 : 0;\n return aOrder - bOrder;\n });\n\n // Any time the line items are updated, we should reset the promo code, as we no longer know if the promo code is valid for the new product selections.\n setPromoCode(null);\n\n setLineItems(newLineItems);\n };\n\n const removeLineItems = existingLineItems => {\n setLineItems(lineItems.filter(li => {\n if (Array.isArray(existingLineItems))\n return !existingLineItems.includes(li);\n\n return li !== existingLineItems;\n }));\n\n // Any time the line items are updated, we should reset the promo code, as we no longer know if the promo code is valid for the new product selections.\n setPromoCode(null);\n };\n\n const [shippingStageState, setShippingStageState] = useState({\n firstName: '',\n lastName: '',\n email: '',\n address1: '',\n address2: '',\n phone: '',\n country: countries[\"US\"],\n city: '',\n state: '',\n zipCode: '',\n password: '',\n passwordConfirmation: '',\n organizationName: '',\n organizationCode: '',\n shippingMethod: shippingMethods.standard,\n isArrivalPhoneCallRequested: false\n });\n\n const [paymentStageState, setPaymentStageState] = useState({\n creditCardNumber: '',\n nameOnCard: '',\n creditCardDate: '',\n maskChar: '',\n cvv: '',\n isUsingBillingAddress: false,\n paymentInputAddress: '',\n paymentInputAddress2: '',\n paymentInputCountry: countries[\"US\"],\n paymentInputCity: '',\n paymentInputState: '',\n paymentInputZip: '',\n }\n );\n\n const [giftSenderInformation, setGiftSenderInformation] = useState({\n name: '',\n email: '',\n message: '',\n giftConfirmationEmail: recipientType.me,\n isGift: false,\n }\n );\n\n // This object will give useful information about the app/environment depending on the purchase flow portal.\n let appInfo;\n if (portal === PORTALS.CLEARSTEP)\n appInfo = { name: \"MyClearStep\", supportEmail: 'support@myclearstep.com', clientPurchaseURL: 'https://store.myclearstep.com/orders/buy-now', iosStoreUrl: 'https://apps.apple.com/us/app/clearstep/id1510599421', androidStoreUrl: 'https://play.google.com/store/apps/details?id=com.shapau.android&hl=en_US&gl=US' };\n else if (portal === PORTALS.SHAPA_HEALTH)\n appInfo = { name: \"Shapa\", supportEmail: 'support@shapa.me', clientPurchaseURL: 'https://www.myshapa.com/orders/buy-now', iosStoreUrl: 'https://apps.apple.com/us/app/shapa-inc/id1053767884', androidStoreUrl: 'https://play.google.com/store/apps/details?id=com.shapa.android' };\n else\n appInfo = { name: \"Shapa\", supportEmail: 'support@shapa.me', clientPurchaseURL: 'https://www.myshapa.com/orders/buy-now', iosStoreUrl: 'https://apps.apple.com/us/app/shapa-inc/id1053767884', androidStoreUrl: 'https://play.google.com/store/apps/details?id=com.shapa.android' };\n\n const contextValue = {\n appInfo,\n PURCHASE_STAGES,\n CLINICIAN_MODES,\n promotional,\n totalPrice,\n order,\n setOrder,\n setTotalPrice,\n selectedPlanId,\n purchaseStage,\n scaleBpBundleProducts,\n scaleProducts,\n bpDeviceProducts,\n planProducts,\n adminAccessProducts,\n setPurchaseStage,\n shippingStageState,\n setShippingStageState,\n paymentStageState,\n setPaymentStageState,\n promoCode,\n setPromoCode,\n salesTaxRates,\n isProduction,\n referralCode,\n referralCodeValid,\n plansConfiguration,\n giftSenderInformation,\n setGiftSenderInformation,\n urlPromoCode,\n adminReferralTargetCode,\n abTestingPage,\n quantitiesEnabled: purchaseFlow === PURCHASE_FLOWS.CLEARSTEP_CLINICIAN || purchaseFlow === PURCHASE_FLOWS.SHAPA_HEALTH_CLINICIAN,\n portal,\n purchaseFlow,\n lineItems,\n setLineItemsByCategory,\n setLineItems,\n removeLineItems,\n npi,\n setNpi,\n deferredUserCreate,\n setDeferredUserCreate,\n clinicianMode,\n setClinicianMode\n };\n\n useEffect(() => {\n history.replaceState({ stage: INITIAL_PURCHASE_STATE }, null, \"\");\n }, []);\n\n const renderContent = () => {\n let content = null;\n const navBar = document.getElementById(\"nav-bar\");\n const buyNowContainer= document.getElementById(\"buy-now-container\");\n if (purchaseStage === PURCHASE_STAGES.PRODUCT_SELECTION) {\n const timeoutId = setTimeout(() => { // we need to setTimeout at first to get NavBar values after it gets rendered.\n navBar.style.visibility = \"visible\";\n buyNowContainer.style.marginTop = `${navBar.offsetHeight + navBar.offsetTop}px`;\n clearTimeout(timeoutId);\n }, 200);\n content = ;\n }\n else if (purchaseStage === PURCHASE_STAGES.CHOOSE_MODE) {\n const timeoutId = setTimeout(() => { // we need to setTimeout at first to get NavBar values after it gets rendered.\n navBar.style.visibility = \"visible\";\n buyNowContainer.style.marginTop = `${navBar.offsetHeight + navBar.offsetTop}px`;\n clearTimeout(timeoutId);\n }, 200);\n content = ;\n }\n else if ([PURCHASE_STAGES.SHIPPING, PURCHASE_STAGES.PAYMENT, PURCHASE_STAGES.REVIEW].includes(purchaseStage)) {\n const timeoutId = setTimeout(() => { // we need to setTimeout at first to get NavBar values after it gets rendered.\n navBar.style.visibility = \"hidden\";\n const orderNav = document.getElementById(\"order-nav\");\n buyNowContainer.style.marginTop = `${orderNav.offsetHeight}px`;\n clearTimeout(timeoutId);\n }, 200);\n content = ;\n }\n else if (purchaseStage === PURCHASE_STAGES.CONFIRMATION) {\n content = ;\n }\n return content;\n };\n\n window.onpopstate = function(event) {\n // Don't let the user to change state if they are on the confirmation page.\n // If we allowed this, a user could potentially create a duplicate order trying to get back to the confirmation page.\n if (purchaseStage === PURCHASE_STAGES.CONFIRMATION)\n return;\n\n if (event.state.stage === PURCHASE_STAGES.PRODUCT_SELECTION)\n setPurchaseStageDirect(PURCHASE_STAGES.PRODUCT_SELECTION);\n else if (event.state.stage === PURCHASE_STAGES.SHIPPING)\n setPurchaseStageDirect(PURCHASE_STAGES.SHIPPING);\n else if (event.state.stage === PURCHASE_STAGES.PAYMENT)\n setPurchaseStageDirect(PURCHASE_STAGES.PAYMENT);\n else if (event.state.stage === PURCHASE_STAGES.REVIEW)\n setPurchaseStageDirect(PURCHASE_STAGES.REVIEW);\n else if (event.state.stage === PURCHASE_STAGES.CHOOSE_MODE)\n setPurchaseStageDirect(PURCHASE_STAGES.CHOOSE_MODE);\n };\n\n return (\n \n \n {renderContent()}\n \n
\n );\n};\n\nBuyNow.propTypes = {\n portal: PropTypes.string.isRequired,\n purchaseFlow: PropTypes.string.isRequired,\n promotional: PropTypes.object,\n scaleBpBundleProducts: PropTypes.array.isRequired,\n scaleProducts: PropTypes.array.isRequired,\n bpDeviceProducts: PropTypes.array.isRequired,\n planProducts: PropTypes.array.isRequired,\n adminAccessProducts: PropTypes.array.isRequired,\n salesTaxRates: PropTypes.array.isRequired,\n isProduction: PropTypes.bool.isRequired,\n countries: CountriesPropTypes.isRequired,\n referralCode: PropTypes.string,\n referralCodeValid: PropTypes.bool.isRequired,\n plansConfiguration: PropTypes.object.isRequired,\n urlPromoCode: PropTypes.string,\n adminReferralTargetCode: PropTypes.string,\n abTestingPage: PropTypes.bool.isRequired\n};\n\n// solution for using hooks in parent component https://github.com/shakacode/react_on_rails/issues/1198#issuecomment-483784251\nexport default BuyNow;\n","import BuyNowErrorBoundary from \"./BuyNowErrorBoundary\";\nimport React from \"react\";\nimport BuyNow from \"./BuyNow\";\n\n/**\n * By using a wrapper for our root component, we can surround all code with an error boundary.\n * For example, our BuyNowErrorBoundary component can capture all unhandled exceptions and log or email them.\n * @param {Object} props\n * @returns {JSX.Element}\n * @constructor\n */\nconst BuyNowWrapper = props => (\n \n \n \n);\n\n// solution for using hooks in parent component https://github.com/shakacode/react_on_rails/issues/1198#issuecomment-483784251\nexport default (props) => ;\n","//= require turbolinks\nimport {} from \"jquery-ujs\";\nimport ReactOnRails from \"react-on-rails\";\n\nimport BuyNowWrapper from \"../bundles/orders/components/BuyNowWrapper\";\n\nReactOnRails.register({\n BuyNowWrapper,\n});\n"],"sourceRoot":""}