src/components/fragments/BeamImpact/BeamNonProfit.jsx

import React, {useState, useEffect, useRef} from "react";
import {Text} from "components/elementsThemed";
import {withErrorBoundary} from "components/hocs";
import {useMutationObserver} from "hooks";
import Axios from "axios";
import {constants} from "utils";
import Lbc from "@lunchboxinc/lunchbox-components";
import Fallback from "./Fallback";
import css from "./beamImpact.module.scss";

const {BEAM_BASE_URL} = constants;

const {Row} = Lbc.Grid;

const POST_TRANSACTION_URL = `${BEAM_BASE_URL}/api/v1/nonprofits/transaction/`;
const Step = {
  POST_CHECKOUT: "post-checkout",
  PRE_CHECKOUT: "pre-checkout",
};

/**
 * Helper method for postTransaction
 *
 * @param {string} widgetId
 * @param {object} data
 */
const POST_BEAM_TRANSACTION = (widgetId, data) => {
  return Axios.post(POST_TRANSACTION_URL, data, {
    headers: {
      "X-WIDGET-ID": widgetId,
    },
  }).then(
    (response) => response,
    (error) => console.error(error),
  );
};

/**
 * Post method for posting Beam transaction data
 *
 * @param {object} beam
 * @param
 * @param selectedNonprofit
 */
const postTransaction = async (beam, selectedNonprofit) => {
  const {store, cart_total, user, nonprofit} = beam?.widget?.transactionData;
  const {widgetId} = beam?.widget?.options;
  let data;
  if (nonprofit) {
    data = {
      cart_total,
      nonprofit,
      store,
      user,
    };
  } else {
    data = {
      cart_total,
      nonprofit: selectedNonprofit,
      store,
      user,
    };
  }
  if (nonprofit || selectedNonprofit)
    await POST_BEAM_TRANSACTION(widgetId, data);
};

/**
 * Component for Beam Nonprofit widget
 *
 * @author Htin Linn Aung
 * @memberof Fragments.Fragments/Beam
 * @param {object} props
 * @param {object} props.style - From theme file & withTemplate HOC
 * @param {string} props.widgetColors - From BeamImpact component
 * @param {string} props.widgetId - Provided by Beam. It is in config
 * @param {string} props.patronEmail - From BeamImpact component
 * @param {number} props.cartTotal - From BeamImpact component
 * @param {string} props.step - Checkout Step
 * @param {object} props.beam - State in pages/Cart/routes.jsx
 * @param {Function} props.setBeam - Setter method for state in pages/Cart/routes.jsx
 * @param {Fuction} props.onSkipBeamSelection - Alternate Func to bring the user to Routes.PURCHASE_COMPLETE - from home.jsx
 * @returns {React.Element} - Component for Beam Nonprofit widget
 */
const BeamNonProfit = ({
  style,
  widgetColors,
  widgetId,
  patronEmail,
  cartTotal,
  step,
  beam,
  setBeam,
  onSkipBeamSelection,
}) => {
  const {labels} = style;
  const {
    explainerTextColor,
    defaultExplainerTextColor,
    descriptionTextColor,
    impactProgressBarColor,
    baseWarningTextColor,
  } = widgetColors;
  const explainerColor = explainerTextColor || defaultExplainerTextColor;
  const [beamAppsLoading, setBeamAppsLoading] = useState(false);
  const [widgetLoading, setWidgetLoading] = useState(false);
  const isPreCheckout = step === Step.PRE_CHECKOUT;
  const beamWidget = useRef(null);
  const getArgs = () => ({
    cartTotal,
    user: patronEmail,
  });

  useEffect(() => {
    if (!widgetId) {
      console.error("[Beam Impact] Widget ID is required.");
      return;
    }

    setBeamAppsLoading(true);
    setWidgetLoading(true);
    const initializeWidget = async () => {
      // Need to add mechanism to handle duplicate instantiation

      const widget = new window.beamApps.NonprofitWidget({
        containerId: "beam-widget-container",
        forceMobileView: "true",
        isPreCheckout,
        loadingScreenContent: "",
        textColor: explainerColor,
        themeConfig: {
          changeButtonColor: baseWarningTextColor,
          descriptionBackgroundColor: explainerColor,
          descriptionTextColor,
          goalTextColor: explainerColor,
          id: "lunchbox-nonprofit",
          progressBarColors: [
            {
              color: impactProgressBarColor,
              offset: "100%",
            },
          ],
          selectedNonprofitCallback: (nonprofit) => {
            if (step === Step.PRE_CHECKOUT) widget.render(getArgs());
            else {
              postTransaction(beam, nonprofit?.id);
              onSkipBeamSelection();
            }
          },
          textColor: explainerColor,
        },
        widgetId,
      });
      await widget.render(getArgs());
      beamWidget.current = widget;
      await setBeam({
        postTransaction,
        widget,
      });
    };

    if (window.beamApps) {
      setBeamAppsLoading(false);
      initializeWidget();
    }
  }, [beamAppsLoading, window.beamApps]);

  useMutationObserver(() => setWidgetLoading(false), {
    attributes: true,
    selector: "#beam-widget-container",
  });

  switch (step) {
    case Step.PRE_CHECKOUT:
      return <div id="beam-widget-container" />;
    default:
      return (
        <>
          <div id="beam-widget-container">
            {widgetLoading && (
              <div className={css.content}>
                <Row spacing={10}>
                  <Text type={labels.primary}>Thank you for your order!</Text>
                </Row>
              </div>
            )}
          </div>
        </>
      );
  }
};

export default withErrorBoundary(BeamNonProfit, Fallback);