All files / src/sections/PayBuilder/Checkout/components CustomFieldsPreview.tsx

100% Statements 11/11
90% Branches 9/10
100% Functions 3/3
100% Lines 10/10

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                                    13x         113x 113x       113x       113x 113x     113x   3x               5x         5x                                                  
import { Stack } from "@mui/material";
import { ClipboardTextIcon } from "@phosphor-icons/react";
import { Controller, useFormContext } from "react-hook-form";
import { GiveInput } from "@shared/GiveInputs/GiveInput";
import { usePayBuilderForm } from "@sections/PayBuilder/provider/PayBuilderFormProvider";
import { useGetFeatureFlagValues } from "FeatureFlags/useGetFeatureFlagValues";
import SectionHeader from "./SectionHeader";
import WithCheckoutTooltip from "./CheckoutTooltip";
import {
  MAX_CUSTOM_FIELD_LABEL_LEN,
  getRenderableCustomFields,
} from "../customFields.helpers";
 
/**
 * Customer-facing / live-preview render of the checkout custom fields
 * (AC011/AC012/AC020/AC022). Same component in the builder preview and the
 * public checkout — it reads the config from the shared builder form.
 */
const CustomFieldsPreview = ({
  isDisabledFields,
}: {
  isDisabledFields?: boolean;
}) => {
  const { isPayBuilderCustomFieldsEnabled } = useGetFeatureFlagValues();
  const { control } = useFormContext();
  const {
    methods: leftSidepanelMethods,
    parsedValues: { checkoutInputStyles },
  } = usePayBuilderForm();
 
  // Subscribe only to the custom-fields subtree — a full-form watch() re-rendered
  // the whole preview on every keystroke in any builder field (name, amount, etc.).
  const customFields = leftSidepanelMethods.watch("Checkout.customFields");
  const fields = getRenderableCustomFields(customFields);
 
  // AC-ROLLOUT + AC003 — nothing renders unless flagged on and configured.
  if (!isPayBuilderCustomFieldsEnabled || fields.length === 0) return null;
 
  return (
    <Stack spacing="20px" data-testid="checkout-custom-fields">
      <SectionHeader
        title={customFields?.sectionTitle || "Additional Information"}
        icon={<ClipboardTextIcon size={24} />}
      />
      <Stack spacing={2}>
        {fields.map((field) => (
          <Controller
            key={field.fieldId}
            control={control}
            name={`customFields.${field.fieldId}`}
            render={({ field: { onChange, value }, fieldState: { error } }) => (
              <WithCheckoutTooltip disabled={isDisabledFields}>
                <GiveInput
                  name={`customFields.${field.fieldId}`}
                  fullWidth
                  // AC012 — required fields are marked with an asterisk.
                  label={`${field.label}${field.isRequired ? " *" : ""}`}
                  value={value ?? ""}
                  onChange={onChange}
                  error={!!error}
                  helperText={error?.message}
                  disabled={isDisabledFields}
                  maxLength={MAX_CUSTOM_FIELD_LABEL_LEN}
                  sx={checkoutInputStyles}
                  data-testid={`checkout-custom-field-${field.fieldId}`}
                />
              </WithCheckoutTooltip>
            )}
          />
        ))}
      </Stack>
    </Stack>
  );
};
 
export default CustomFieldsPreview;