All files / src/sections/PayBuilder/components BottomLeftPanelActions.tsx

71.42% Statements 35/49
52.94% Branches 18/34
77.77% Functions 7/9
75.55% Lines 34/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190                                    11x   11x 531x               531x   531x 531x 531x 531x 531x       531x 531x               531x 531x     531x   531x             531x 54x 41x       531x           8x                             8x   8x 8x                 531x                                                       11x                   531x                 531x   6x   6x   6x   531x                           8x 8x                                   11x  
import { Box, CircularProgress } from "@mui/material";
import GiveButton from "@shared/Button/GiveButton";
import { useCustomThemeV2 } from "@theme/hooks/useCustomThemeV2";
import { memo, useCallback, useEffect } from "react";
import { useCart } from "../provider/CartContext";
import { usePayBuilderContext } from "../provider/PayBuilderContext";
import { usePayBuilderForm } from "../provider/PayBuilderFormProvider";
import useOpenCampaignPanel from "../hooks/useOpenCampaignPanel";
import { useParams } from "react-router-dom";
import { showMessage } from "@common/Toast";
import useCheckFormType from "./hooks/useCheckFormType";
import { useGetSeatingRows } from "@hooks/merchant-api/events/useSeatingRows";
import {
  getSeatingPublishBlock,
  SEATING_PUBLISH_BLOCK_MESSAGE,
} from "../seating.helpers";
 
// The Tickets step (id) — where per-ticket seating errors render, so a blocked commit lands there.
const TICKETS_STEP_ID = "variants_creation";
 
export const BottomLeftPanelActions = () => {
  const { isMediumView } = useCustomThemeV2();
  const {
    activeStepIndex,
    isLastStep,
    stepsArray,
    goToStep,
    isNextDisabled,
    setLastStepCompleted,
  } = usePayBuilderContext();
 
  const { clearCart } = useCart();
  const { mutate, isLoading, methods } = usePayBuilderForm();
  const { openCampaignPanel } = useOpenCampaignPanel();
  const { editID } = useParams();
  const { isEvent } = useCheckFormType();
 
  // PAY Builder 031 — the persisted seating rows (with ids) used to check every ticket is
  // assigned before a commit. Disabled for non-events / before the product exists.
  const seatingProductId = methods.watch("productId");
  const { data: seatingConfig } = useGetSeatingRows(
    seatingProductId,
    Boolean(seatingProductId && isEvent),
  );
 
  const {
    formState: { errors, isValid },
    watch,
  } = methods;
  const values = watch();
 
  const isPublished =
    methods.watch("publishedStatus") === "public" && isLastStep;
 
  const isDisabled = isNextDisabled({
    values,
    errors,
    isLoading,
    isValid,
  });
 
  useEffect(() => {
    if (isDisabled) {
      setLastStepCompleted(activeStepIndex);
    }
  }, [isDisabled]);
 
  const onConfirm = useCallback(() => {
    // PAY Builder 031 — block a commit that would silently half-save a seating event: the
    // product PATCH + publish succeed while a seatless ticket's variant create is rejected by
    // the BE (ErrTicketNeedsSeatingRow). Only the final commit (last step: Publish / Save)
    // sends the publish; guard it and send the merchant to the Tickets step, where each
    // offending ticket already shows an inline "Assigning rows to this ticket is required".
    Iif (isLastStep) {
      const vals = methods.getValues();
      const block = getSeatingPublishBlock(
        isEvent,
        Boolean(vals?.DateLocation?.assignSeating),
        vals?.Items ?? [],
        seatingConfig?.rows ?? [],
      );
      if (block) {
        showMessage("Error", SEATING_PUBLISH_BLOCK_MESSAGE[block]);
        const ticketsIndex = stepsArray.findIndex((s) => s.id === TICKETS_STEP_ID);
        if (ticketsIndex >= 0) goToStep({ index: ticketsIndex, free: true });
        return;
      }
    }
    mutate({
      handleSuccessCB: () => {
        !isLastStep && goToStep({ offSet: 1 });
        Iif (isLastStep) {
          clearCart();
          openCampaignPanel(editID, isPublished);
        }
      },
      publish: methods.watch("publishedStatus") !== "public",
    });
  }, [isLastStep, isEvent, seatingConfig, stepsArray, goToStep, mutate, clearCart, openCampaignPanel, editID, isPublished, methods]);
 
  return (
    <>
      <GiveButton
        size="large"
        sx={{
          marginLeft: isMediumView ? 0 : "auto",
          visibility: activeStepIndex !== 0 ? "visible" : "hidden",
        }}
        variant="ghost"
        label="Back"
        onClick={(e) => {
          e.stopPropagation();
          goToStep({ offSet: -1 });
        }}
      />
      <ConfirmButton
        isPublished={isPublished}
        isDisabled={isDisabled}
        isLastStep={isLastStep}
        stepsArray={stepsArray}
        isMediumView={isMediumView}
        isLoading={isLoading}
        onClick={onConfirm}
      />
    </>
  );
};
 
const ConfirmButton = memo(
  ({
    isPublished,
    isDisabled,
    isLastStep,
    onClick,
    isMediumView,
    stepsArray,
    isLoading,
  }: any) => {
    const getButtonLabel = ({
      isLastStep,
      isPublished,
      stepsArray,
    }: {
      isLastStep: boolean;
      isPublished: boolean;
      stepsArray: Array<{ label: string; id: string }>;
    }) => {
      if (!isLastStep) return "Next";
 
      Iif (isPublished) return "Save";
 
      const a = stepsArray;
 
      return a[a.length - 1].label === "Send" ? "Send" : "Publish";
    };
    return (
      <Box
        sx={{
          position: "relative",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <GiveButton
          disabled={isDisabled}
          size="large"
          label={getButtonLabel({ isLastStep, isPublished, stepsArray })}
          onClick={(e) => {
            e.stopPropagation();
            onClick?.();
          }}
          sx={{
            marginLeft: isMediumView ? "auto" : 0,
          }}
        />
        {isLoading && (
          <CircularProgress
            style={{
              position: "absolute",
            }}
          />
        )}
      </Box>
    );
  },
);
 
ConfirmButton.displayName = "ConfirmButton";