# ai-builder/2.7.10/assets/js/src/editor-blocks/cards/index.js

AI Builder – Generate pages, blocks, images &amp; translate with AI, version 2.7.10. 866 lines.

- Page: https://pluginprobe.com/plugins/ai-builder/2.7.10/code/assets/js/src/editor-blocks/cards/index.js
- Raw: https://pluginprobe.com/plugins/ai-builder/2.7.10/raw/assets/js/src/editor-blocks/cards/index.js
- Modified: 2025-10-02T09:28:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ai-builder/2.7.10/code/assets/js/src/editor-blocks/cards/index.js#L10-L20`.

```javascript
const { registerBlockType } = wp.blocks;
const { __ } = wp.i18n;
const {
  PanelBody,
  TextControl,
  TextareaControl,
  Button,
  Notice,
  ToggleControl,
  RangeControl,
} = wp.components;
const { InspectorControls, useBlockProps, MediaUpload, MediaUploadCheck } =
  wp.blockEditor || wp.editor;

// Use HTML5 color input instead of WordPress ColorPicker to avoid compatibility issues
const SafeColorPicker = ({ color, onChange, disableAlpha }) => (
  <div style={{ position: "relative" }}>
    <input
      type="color"
      value={color || "#4f46e5"}
      onChange={(e) => onChange && onChange(e.target.value)}
      style={{
        width: "100%",
        height: "40px",
        border: "1px solid #ddd",
        borderRadius: "4px",
        cursor: "pointer",
      }}
    />
  </div>
);

// WordPress MediaUpload component wrapper
const ImageSelector = ({ onSelect, imageUrl, imageId, label }) => {
  const onSelectImage = (media) => {
    onSelect({
      url: media.url,
      id: media.id,
    });
  };

  const onRemoveImage = () => {
    onSelect({
      url: "",
      id: "",
    });
  };

  return (
    <div>
      <div
        className="aibui-card-image"
        style={{
          width: "100%",
          height: "120px",
          backgroundImage: imageUrl ? `url(${imageUrl})` : "none",
          backgroundSize: "cover",
          backgroundPosition: "center",
          borderRadius: "6px",
          marginBottom: "8px",
          border: "1px solid #e0e0e0",
        }}
      />
      <div style={{ display: "flex", gap: "8px" }}>
        <Button
          onClick={() => {
            if (typeof wp === "undefined" || !wp.media) {
              alert(
                __(
                  "WordPress media library is not available. Please refresh the page.",
                  "ai-builder"
                )
              );
              return;
            }

            const mediaUploader = wp.media({
              title: __("Select Image", "ai-builder"),
              button: {
                text: __("Use This Image", "ai-builder"),
              },
              multiple: false,
              library: {
                type: "image",
              },
            });

            mediaUploader.on("select", function () {
              const attachment = mediaUploader
                .state()
                .get("selection")
                .first()
                .toJSON();
              console.log("Selected attachment:", attachment);

              if (attachment && attachment.url) {
                onSelectImage(attachment);
              } else {
                console.error(
                  "No attachment selected or attachment missing URL"
                );
              }
            });

            mediaUploader.open();
          }}
          isSecondary
        >
          {imageUrl
            ? __("Change Image", "ai-builder")
            : __("Select Image", "ai-builder")}
        </Button>
        {imageUrl && (
          <Button onClick={onRemoveImage} isDestructive isSmall>
            {__("Remove", "ai-builder")}
          </Button>
        )}
      </div>
    </div>
  );
};

registerBlockType("ai-builder/aibui-cards", {
  title: __("Product Cards (AI Builder)", "ai-builder"),
  icon: "grid-view",
  category: "widgets",
  supports: { align: ["wide", "full"], html: false },
  attributes: {
    cards: {
      type: "array",
      default: [
        {
          id: "card1",
          title: "Premium Product",
          description:
            "High-quality product with excellent features and great value for money.",
          price: "$99.99",
          originalPrice: "$149.99",
          imageUrl: "",
          imageId: "",
          buttonText: "Buy Now",
          buttonLink: "",
          buttonColor: "#4f46e5",
          buttonTextColor: "#ffffff",
          badge: "Sale",
          badgeColor: "#ef4444",
        },
      ],
    },
    columns: { type: "number", default: 3 },
    cardSpacing: { type: "number", default: 20 },
    cardBackground: { type: "string", default: "#ffffff" },
    cardBorderRadius: { type: "number", default: 12 },
    showBadges: { type: "boolean", default: true },
    defaultImageUrl: { type: "string", default: "" },
    defaultImageId: { type: "number", default: 0 },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const {
      cards,
      columns,
      cardSpacing,
      cardBackground,
      cardBorderRadius,
      showBadges,
      defaultImageUrl,
      defaultImageId,
    } = attributes;
    const blockProps = useBlockProps({ className: "aibui-cards" });

    const addCard = () => {
      const newCard = {
        id: `card${Date.now()}`,
        title: "New Product",
        description: "Product description here.",
        price: "$49.99",
        originalPrice: "",
        imageUrl: defaultImageUrl || "",
        imageId: defaultImageId || "",
        buttonText: "Buy Now",
        buttonLink: "",
        buttonColor: "#4f46e5",
        buttonTextColor: "#ffffff",
        badge: "New",
        badgeColor: "#10b981",
      };
      setAttributes({
        cards: [...cards, newCard],
      });
    };

    const removeCard = (cardId) => {
      setAttributes({
        cards: cards.filter((card) => card.id !== cardId),
      });
    };

    const updateCard = (cardId, field, value) => {
      setAttributes({
        cards: cards.map((card) =>
          card.id === cardId ? { ...card, [field]: value } : card
        ),
      });
    };

    const applyDefaultImageToAll = () => {
      if (!defaultImageUrl) return;
      setAttributes({
        cards: cards.map((card) =>
          !card.imageUrl
            ? { ...card, imageUrl: defaultImageUrl, imageId: defaultImageId }
            : card
        ),
      });
    };

    return (
      <div {...blockProps}>
        <InspectorControls>
          <PanelBody title={__("Cards Settings", "ai-builder")} initialOpen>
            <RangeControl
              label={__("Number of columns", "ai-builder")}
              value={columns}
              onChange={(val) => setAttributes({ columns: val || 3 })}
              min={1}
              max={4}
            />

            <RangeControl
              label={__("Card spacing (px)", "ai-builder")}
              value={cardSpacing}
              onChange={(val) => setAttributes({ cardSpacing: val || 20 })}
              min={10}
              max={40}
            />

            <div style={{ marginTop: "12px" }}>
              <p style={{ margin: "0 0 8px" }}>
                {__("Card background color", "ai-builder")}
              </p>
              <SafeColorPicker
                color={cardBackground}
                onChange={(val) =>
                  setAttributes({ cardBackground: val || "#ffffff" })
                }
              />
            </div>

            <RangeControl
              label={__("Card border radius (px)", "ai-builder")}
              value={cardBorderRadius}
              onChange={(val) => setAttributes({ cardBorderRadius: val || 12 })}
              min={0}
              max={30}
            />

            <ToggleControl
              label={__("Show badges", "ai-builder")}
              checked={showBadges}
              onChange={(val) => setAttributes({ showBadges: !!val })}
            />

            <div
              style={{
                marginTop: "20px",
                paddingTop: "20px",
                borderTop: "1px solid #e0e0e0",
              }}
            >
              <h4 style={{ margin: "0 0 12px 0" }}>
                {__("Default Image", "ai-builder")}
              </h4>
              <p
                style={{
                  margin: "0 0 12px 0",
                  fontSize: "13px",
                  color: "#666",
                }}
              >
                {__(
                  "Set a default image for cards without images",
                  "ai-builder"
                )}
              </p>
              <ImageSelector
                onSelect={(media) => {
                  setAttributes({
                    defaultImageUrl: media.url,
                    defaultImageId: media.id,
                  });
                }}
                imageUrl={defaultImageUrl}
                imageId={defaultImageId}
                label={__("Default Image", "ai-builder")}
              />
              {defaultImageUrl && (
                <Button
                  onClick={applyDefaultImageToAll}
                  isPrimary
                  isSmall
                  style={{ marginTop: "8px" }}
                >
                  {__("Apply to Empty Cards", "ai-builder")}
                </Button>
              )}
            </div>

            <div style={{ marginTop: "16px" }}>
              <h4>{__("Cards", "ai-builder")}</h4>
              {cards.map((card, index) => (
                <div
                  key={card.id}
                  style={{
                    border: "1px solid #e0e0e0",
                    borderRadius: "8px",
                    padding: "12px",
                    marginBottom: "12px",
                    backgroundColor: "#f9f9f9",
                  }}
                >
                  <div
                    style={{
                      display: "flex",
                      justifyContent: "space-between",
                      alignItems: "center",
                      marginBottom: "8px",
                    }}
                  >
                    <strong>
                      {__("Card", "ai-builder")} {index + 1}
                    </strong>
                    <Button
                      isDestructive
                      isSmall
                      onClick={() => removeCard(card.id)}
                    >
                      {__("Remove", "ai-builder")}
                    </Button>
                  </div>

                  <TextControl
                    label={__("Product title", "ai-builder")}
                    value={card.title}
                    onChange={(val) => updateCard(card.id, "title", val || "")}
                  />

                  <TextareaControl
                    label={__("Description", "ai-builder")}
                    value={card.description}
                    onChange={(val) =>
                      updateCard(card.id, "description", val || "")
                    }
                    rows={3}
                  />

                  <div style={{ display: "flex", gap: "12px" }}>
                    <TextControl
                      label={__("Price", "ai-builder")}
                      value={card.price}
                      onChange={(val) =>
                        updateCard(card.id, "price", val || "")
                      }
                      style={{ flex: 1 }}
                    />
                    <TextControl
                      label={__("Original price (optional)", "ai-builder")}
                      value={card.originalPrice}
                      onChange={(val) =>
                        updateCard(card.id, "originalPrice", val || "")
                      }
                      style={{ flex: 1 }}
                    />
                  </div>

                  <div style={{ marginBottom: "16px" }}>
                    <p style={{ margin: "0 0 8px", fontWeight: "500" }}>
                      {__("Product Image", "ai-builder")}
                    </p>
                    <ImageSelector
                      onSelect={(media) => {
                        console.log("Updating card with media:", media);
                        console.log("Card ID:", card.id);
                        console.log("Current card:", card);

                        // Update both fields in a single setAttributes call
                        setAttributes({
                          cards: cards.map((c) =>
                            c.id === card.id
                              ? { ...c, imageUrl: media.url, imageId: media.id }
                              : c
                          ),
                        });

                        console.log(
                          "Updated cards:",
                          cards.map((c) =>
                            c.id === card.id
                              ? { ...c, imageUrl: media.url, imageId: media.id }
                              : c
                          )
                        );
                      }}
                      imageUrl={card.imageUrl}
                      imageId={card.imageId}
                      label={__("Product Image", "ai-builder")}
                    />
                  </div>

                  <div style={{ display: "flex", gap: "12px" }}>
                    <TextControl
                      label={__("Button text", "ai-builder")}
                      value={card.buttonText}
                      onChange={(val) =>
                        updateCard(card.id, "buttonText", val || "")
                      }
                      style={{ flex: 1 }}
                    />
                    <TextControl
                      label={__("Button link", "ai-builder")}
                      value={card.buttonLink}
                      onChange={(val) =>
                        updateCard(card.id, "buttonLink", val || "")
                      }
                      style={{ flex: 1 }}
                    />
                  </div>

                  <div
                    style={{ display: "flex", gap: "12px", marginTop: "12px" }}
                  >
                    <div style={{ flex: 1 }}>
                      <p style={{ margin: "0 0 8px" }}>
                        {__("Button color", "ai-builder")}
                      </p>
                      <SafeColorPicker
                        color={card.buttonColor}
                        onChange={(val) =>
                          updateCard(card.id, "buttonColor", val || "#4f46e5")
                        }
                      />
                    </div>
                    <div style={{ flex: 1 }}>
                      <p style={{ margin: "0 0 8px" }}>
                        {__("Button text color", "ai-builder")}
                      </p>
                      <SafeColorPicker
                        color={card.buttonTextColor}
                        onChange={(val) =>
                          updateCard(
                            card.id,
                            "buttonTextColor",
                            val || "#ffffff"
                          )
                        }
                      />
                    </div>
                  </div>

                  <div
                    style={{ display: "flex", gap: "12px", marginTop: "12px" }}
                  >
                    <TextControl
                      label={__("Badge text", "ai-builder")}
                      value={card.badge}
                      onChange={(val) =>
                        updateCard(card.id, "badge", val || "")
                      }
                      style={{ flex: 1 }}
                    />
                    <div style={{ flex: 1 }}>
                      <p style={{ margin: "0 0 8px" }}>
                        {__("Badge color", "ai-builder")}
                      </p>
                      <SafeColorPicker
                        color={card.badgeColor}
                        onChange={(val) =>
                          updateCard(card.id, "badgeColor", val || "#ef4444")
                        }
                      />
                    </div>
                  </div>
                </div>
              ))}

              <Button isPrimary onClick={addCard}>
                {__("Add Card", "ai-builder")}
              </Button>
            </div>
          </PanelBody>
        </InspectorControls>

        <CardsPreview
          cards={cards}
          columns={columns}
          cardSpacing={cardSpacing}
          cardBackground={cardBackground}
          cardBorderRadius={cardBorderRadius}
          showBadges={showBadges}
          defaultImageUrl={defaultImageUrl}
          defaultImageId={defaultImageId}
        />
      </div>
    );
  },
  save: (props) => {
    const { attributes } = props;
    const {
      cards,
      columns,
      cardSpacing,
      cardBackground,
      cardBorderRadius,
      showBadges,
      defaultImageUrl,
      defaultImageId,
    } = attributes;
    const blockProps = wp.blockEditor
      ? wp.blockEditor.useBlockProps.save({ className: "aibui-cards" })
      : {};

    return (
      <div {...blockProps}>
        <div
          className="aibui-cards-container"
          style={{
            display: "grid",
            gridTemplateColumns: `repeat(${columns}, 1fr)`,
            gap: `${cardSpacing}px`,
            maxWidth: "1200px",
            margin: "0 auto",
          }}
        >
          {cards.map((card) => (
            <div
              key={card.id}
              className="aibui-single-card"
              style={{
                backgroundColor: cardBackground,
                borderRadius: `${cardBorderRadius}px`,
                padding: "20px",
                boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
                border: "1px solid #e5e7eb",
                position: "relative",
                transition: "transform 0.2s ease, box-shadow 0.2s ease",
              }}
            >
              {showBadges && card.badge && (
                <div
                  className="aibui-card-badge"
                  style={{
                    position: "absolute",
                    top: "12px",
                    right: "12px",
                    backgroundColor: card.badgeColor,
                    color: "#ffffff",
                    padding: "4px 8px",
                    borderRadius: "4px",
                    fontSize: "12px",
                    fontWeight: "600",
                    textTransform: "uppercase",
                  }}
                >
                  {card.badge}
                </div>
              )}

              {(card.imageUrl || defaultImageUrl) && (
                <div
                  className="aibui-card-image"
                  style={{
                    width: "100%",
                    height: "200px",
                    backgroundImage: `url(${card.imageUrl || defaultImageUrl})`,
                    backgroundSize: "cover",
                    backgroundPosition: "center",
                    borderRadius: "8px",
                    marginBottom: "16px",
                  }}
                />
              )}

              <h3
                className="aibui-card-title"
                style={{
                  fontSize: "18px",
                  fontWeight: "600",
                  margin: "0 0 8px 0",
                  color: "#111827",
                }}
              >
                {card.title}
              </h3>

              <p
                className="aibui-card-description"
                style={{
                  fontSize: "14px",
                  color: "#6b7280",
                  margin: "0 0 16px 0",
                  lineHeight: "1.5",
                }}
              >
                {card.description}
              </p>

              <div
                className="aibui-card-pricing"
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "8px",
                  marginBottom: "16px",
                }}
              >
                <span
                  className="aibui-card-price"
                  style={{
                    fontSize: "20px",
                    fontWeight: "700",
                    color: "#111827",
                  }}
                >
                  {card.price}
                </span>
                {card.originalPrice && (
                  <span
                    className="aibui-card-original-price"
                    style={{
                      fontSize: "14px",
                      color: "#9ca3af",
                      textDecoration: "line-through",
                    }}
                  >
                    {card.originalPrice}
                  </span>
                )}
              </div>

              {card.buttonLink ? (
                <a
                  href={card.buttonLink}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="aibui-card-button"
                  style={{
                    display: "block",
                    width: "100%",
                    backgroundColor: card.buttonColor,
                    color: card.buttonTextColor,
                    padding: "12px 16px",
                    borderRadius: "8px",
                    textAlign: "center",
                    textDecoration: "none",
                    fontSize: "14px",
                    fontWeight: "600",
                    transition: "opacity 0.2s ease",
                    boxSizing: "border-box",
                  }}
                >
                  {card.buttonText}
                </a>
              ) : (
                <button
                  className="aibui-card-button"
                  style={{
                    width: "100%",
                    backgroundColor: card.buttonColor,
                    color: card.buttonTextColor,
                    padding: "12px 16px",
                    borderRadius: "8px",
                    border: "none",
                    fontSize: "14px",
                    fontWeight: "600",
                    cursor: "pointer",
                    transition: "opacity 0.2s ease",
                    boxSizing: "border-box",
                  }}
                >
                  {card.buttonText}
                </button>
              )}
            </div>
          ))}
        </div>
      </div>
    );
  },
});

function CardsPreview({
  cards,
  columns,
  cardSpacing,
  cardBackground,
  cardBorderRadius,
  showBadges,
  defaultImageUrl,
  defaultImageId,
}) {
  return (
    <div
      className="aibui-cards-container"
      style={{
        display: "grid",
        gridTemplateColumns: `repeat(${columns}, 1fr)`,
        gap: `${cardSpacing}px`,
        maxWidth: "1200px",
        margin: "0 auto",
      }}
    >
      {cards.map((card) => (
        <div
          key={card.id}
          className="aibui-single-card"
          style={{
            backgroundColor: cardBackground,
            borderRadius: `${cardBorderRadius}px`,
            padding: "20px",
            boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
            border: "1px solid #e5e7eb",
            position: "relative",
            transition: "transform 0.2s ease, box-shadow 0.2s ease",
          }}
        >
          {showBadges && card.badge && (
            <div
              className="aibui-card-badge"
              style={{
                position: "absolute",
                top: "12px",
                right: "12px",
                backgroundColor: card.badgeColor,
                color: "#ffffff",
                padding: "4px 8px",
                borderRadius: "4px",
                fontSize: "12px",
                fontWeight: "600",
                textTransform: "uppercase",
              }}
            >
              {card.badge}
            </div>
          )}

          {(card.imageUrl || defaultImageUrl) && (
            <div
              className="aibui-card-image"
              style={{
                width: "100%",
                height: "200px",
                backgroundImage: `url(${card.imageUrl || defaultImageUrl})`,
                backgroundSize: "cover",
                backgroundPosition: "center",
                borderRadius: "8px",
                marginBottom: "16px",
              }}
            />
          )}

          <h3
            className="aibui-card-title"
            style={{
              fontSize: "18px",
              fontWeight: "600",
              margin: "0 0 8px 0",
              color: "#111827",
            }}
          >
            {card.title}
          </h3>

          <p
            className="aibui-card-description"
            style={{
              fontSize: "14px",
              color: "#6b7280",
              margin: "0 0 16px 0",
              lineHeight: "1.5",
            }}
          >
            {card.description}
          </p>

          <div
            className="aibui-card-pricing"
            style={{
              display: "flex",
              alignItems: "center",
              gap: "8px",
              marginBottom: "16px",
            }}
          >
            <span
              className="aibui-card-price"
              style={{
                fontSize: "20px",
                fontWeight: "700",
                color: "#111827",
              }}
            >
              {card.price}
            </span>
            {card.originalPrice && (
              <span
                className="aibui-card-original-price"
                style={{
                  fontSize: "14px",
                  color: "#9ca3af",
                  textDecoration: "line-through",
                }}
              >
                {card.originalPrice}
              </span>
            )}
          </div>

          {card.buttonLink ? (
            <a
              href={card.buttonLink}
              target="_blank"
              rel="noopener noreferrer"
              className="aibui-card-button"
              style={{
                display: "block",
                width: "100%",
                backgroundColor: card.buttonColor,
                color: card.buttonTextColor,
                padding: "12px 16px",
                borderRadius: "8px",
                textAlign: "center",
                textDecoration: "none",
                fontSize: "14px",
                fontWeight: "600",
                transition: "opacity 0.2s ease",
                boxSizing: "border-box",
              }}
            >
              {card.buttonText}
            </a>
          ) : (
            <button
              className="aibui-card-button"
              style={{
                width: "100%",
                backgroundColor: card.buttonColor,
                color: card.buttonTextColor,
                padding: "12px 16px",
                borderRadius: "8px",
                border: "none",
                fontSize: "14px",
                fontWeight: "600",
                cursor: "pointer",
                transition: "opacity 0.2s ease",
                boxSizing: "border-box",
              }}
            >
              {card.buttonText}
            </button>
          )}
        </div>
      ))}
    </div>
  );
}

```
