Pricing

Basic

Basic product

$19/month

Best offer for small companies

  • Free Editor
  • 1000 emails limit
  • 10GB inbox
Buy now!

Pro

Expert product

$79/month

No limits! Best option for huge companies

  • Unlimited inboxes
  • Unlimited emails
  • Unlimited space
Buy now!

Basic

Basic product

$19/month

Best offer for small companies

  • Free Editor
  • 1000 emails limit
  • 10GB inbox
Buy now!

Pro

Pro product

$79/month

No limits! Best option for huge companies

  • Unlimited inboxes
  • Unlimited emails
  • Unlimited space
Buy now!

Basic

Basic product

$19/month

Best offer for small companies

  • Free Editor
  • 1000 emails limit
  • 10GB inbox
Buy now!
Decorative

Standard

Basic product

$39/month

Great choice for companies winth more than 10 employees

  • 5000 emails limit
  • 100GB inbox
  • Next super feature
  • Custom integrations
Buy now!

block.json

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "wlc/pricing",
    "title": " Pricing",
    "category": "theme",
    "icon": "tag",
    "description": "Pricing",
    "keywords": [ "pricing" ],
    "attributes":{
        "isFeatured": {
          "type": "boolean",
          "default": false
        },
        "featuredLabel": {
          "type": "string",
          "default": "Featured"
        },
        "title": {
          "type": "string",
          "default": "Pricing"
        },
        "description": {
          "type": "string",
          "default": "New product"
        },
        "currency": {
          "type": "string",
          "default": "$"
        },
        "currencyPosition": {
          "type": "string",
          "default": "before"
        },
        "price": {
          "type": "string",
          "default": "0"
        },
        "period": {
          "type": "string",
          "default": "month"
        },
        "buttonLabel": {
          "type": "string",
          "default": "Button"
        },
        "buttonUrl": {
          "type": "string",
          "default": "#"
        },
        "buttonBackgroundColor": {
          "type": "string",
          "default": "#000000"
        },
        "buttonTextColor": {
          "type": "string",
          "default": "#FFFFFF"
        },
        "buttonTextColorOnHover": {
          "type": "string",
          "default": "#000000"
        },
        "buttonBackgroundColorOnHover": {
          "type": "string",
          "default": "#FFFFFF"
        },
        "priceDescription": {
          "type": "string",
          "default": "Text below price"
        },
        "features": {
          "type": "array",
          "default": [
            "Feature 1",
            "Feature 2",
            "Feature 3"
          ]
        },
        "isDarkMode": {
          "type": "boolean",
          "default": false
        },
        "darkModeBackgroundColor": {
          "type": "string",
          "default": "#000000"
        },
        "darkModeTextColor": {
          "type": "string",
          "default": "#FFFFFF"
        },
        "buttonPosition": {
          "type": "string",
          "default": "bottom"
        },
        "imageUrl": {
          "type": "string",
          "default": ""
        }
    },
    "supports": {
        "anchor": true,
        "align": true,
        "color": {
            "gradient": true
        },
        "spacing": {
            "margin": [ "vertical" ],
            "padding": true
        },
        "typography": {
            "fontSize": true,
            "lineHeight": true
        },
        "renaming": true
    },
    "version": "1.0.0",
    "textdomain": "WLC",
    "editorScript": "file:./index.js",
    "script": "file:./view.js",
    "style": "file:./style-index.css"
  }

edit.js

import {
  InspectorControls,
  useBlockProps,
  RichText,
  URLInput,
  MediaUpload,
  MediaUploadCheck,
} from "@wordpress/block-editor";
import {
  TextControl,
  PanelBody,
  Button,
  ToggleControl,
  Icon,
  ColorPicker,
  SelectControl,
} from "@wordpress/components";
import { __ } from "@wordpress/i18n";
import { useState, useCallback, useRef } from "react";
import { close } from "@wordpress/icons";

const edit = ({ attributes, setAttributes }) => {
  const renderCount = useRef(0);
  renderCount.current += 1;

  const {
    title,
    description,
    currency,
    currencyPosition,
    price,
    features,
    period,
    priceDescription,
    buttonLabel,
    buttonUrl,
    buttonPosition,
    buttonBackgroundColor,
    buttonTextColor,
    buttonTextColorOnHover,
    buttonBackgroundColorOnHover,
    isFeatured,
    featuredLabel,
    isDarkMode,
    darkModeBackgroundColor,
    darkModeTextColor,
    imageUrl,
  } = attributes;

  const blockProps = useBlockProps({
    className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
    style: {
      "--pricing-button-text-color": buttonTextColor,
      "--pricing-button-text-color-on-hover": buttonTextColorOnHover,
      "--pricing-button-bg-color": buttonBackgroundColor,
      "--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
    },
  });

  const [newFeature, setNewFeature] = useState("");

  const handleAddFeature = useCallback(() => {
    if (newFeature.trim() !== "") {
      setAttributes({ features: [...features, newFeature] });
      setNewFeature("");
    }
  }, [newFeature, features, setAttributes]);

  const handleRemoveFeature = useCallback(
    (index) => {
      const updatedFeatures = features.filter((_, i) => i !== index);
      setAttributes({ features: updatedFeatures });
    },
    [features, setAttributes],
  );

  const handleFeatureChange = useCallback(
    (value, index) => {
      const updatedFeatures = [...features];
      updatedFeatures[index] = value;
      setAttributes({ features: updatedFeatures });
    },
    [features, setAttributes],
  );

  const handleColorChange = (color, colorType) => {
    setAttributes({ [colorType]: color.hex });
  };

  const MediaUploader = () => {
    const onSelectImage = useCallback(
      (media) => {
        setAttributes({ imageUrl: media.url });
      },
      [setAttributes],
    );

    const getImageName = useCallback((url) => {
      if (!url) return "";
      const parts = url.split("/");
      return parts[parts.length - 1];
    }, []);

    const onRemoveImage = useCallback(() => {
      setAttributes({ imageUrl: "" });
    }, [setAttributes]);

    return (
      <MediaUploadCheck>
        <MediaUpload
          onSelect={onSelectImage}
          allowedTypes={["image"]}
          value={imageUrl}
          render={({ open }) => (
            <div className="components-base-control">
              {imageUrl ? (
                <>
                  <div>{__("Uploaded Image", "WLC")}</div>
                  <Button onClick={open} isDefault>
                    {getImageName(imageUrl)}
                  </Button>
                  <Button onClick={onRemoveImage} isDestructive>
                    {__("Remove Image", "WLC")}
                  </Button>
                </>
              ) : (
                <Button onClick={open} isDefault>
                  {__("Upload Image", "WLC")}
                </Button>
              )}
            </div>
          )}
        />
      </MediaUploadCheck>
    );
  };

  const PricingButton = () => {
    return (
      buttonUrl &&
      buttonLabel && (
        <a href={buttonUrl} className="pricing-block__button">
          {buttonLabel}
        </a>
      )
    );
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Pricing Settings", "WLC")}>
          <ToggleControl
            label={__("Featured", "WLC")}
            checked={isFeatured}
            onChange={(value) => setAttributes({ isFeatured: value })}
          />
          {isFeatured && (
            <TextControl
              label={__("Featured Label", "WLC")}
              value={featuredLabel}
              onChange={(value) => setAttributes({ featuredLabel: value })}
              placeholder={__("Enter featured label...", "WLC")}
            />
          )}
          <MediaUploader />
          <TextControl
            label={__("Title", "WLC")}
            value={title}
            onChange={(value) => setAttributes({ title: value })}
          />
          <TextControl
            label={__("Description", "WLC")}
            value={description}
            onChange={(value) => setAttributes({ description: value })}
          />
          <TextControl
            label={__("Currency", "WLC")}
            value={currency}
            onChange={(value) => setAttributes({ currency: value })}
          />
          <SelectControl
            label={__("Currency Position", "WLC")}
            value={currencyPosition}
            options={[
              { label: __("Before price", "WLC"), value: "before" },
              { label: __("After price", "WLC"), value: "after" },
            ]}
            onChange={(value) => setAttributes({ currencyPosition: value })}
          />
          <TextControl
            label={__("Price", "WLC")}
            value={price}
            onChange={(value) => setAttributes({ price: value })}
          />
          <TextControl
            label={__("Period", "WLC")}
            value={period}
            onChange={(value) => setAttributes({ period: value })}
          />
          <TextControl
            label={__("Price description", "WLC")}
            value={priceDescription}
            onChange={(value) => setAttributes({ priceDescription: value })}
          />
          <SelectControl
            label={__("Button Position", "WLC")}
            value={buttonPosition}
            options={[
              { label: __("Bottom Section", "WLC"), value: "bottom" },
              { label: __("Top Section", "WLC"), value: "top" },
            ]}
            onChange={(value) => setAttributes({ buttonPosition: value })}
          />
          <TextControl
            label={__("Button Label", "WLC")}
            value={buttonLabel}
            onChange={(value) => setAttributes({ buttonLabel: value })}
          />
          <URLInput
            label={__("Button URL", "WLC")}
            value={buttonUrl}
            onChange={(value) => setAttributes({ buttonUrl: value })}
          />
        </PanelBody>
      </InspectorControls>

      <InspectorControls group="styles">
        <PanelBody title={__("Pricing Styles", "WLC")}>
          <ToggleControl
            label={__("Dark Mode", "WLC")}
            checked={isDarkMode}
            onChange={(value) => setAttributes({ isDarkMode: value })}
          />
          {isDarkMode && (
            <>
              <div>
                <label>{__("Dark Mode Background Color", "WLC")}</label>
                <ColorPicker
                  color={darkModeBackgroundColor}
                  onChangeComplete={(color) =>
                    handleColorChange(color, "darkModeBackgroundColor")
                  }
                  disableAlpha
                />
              </div>
              <div>
                <label>{__("Dark Mode Text Color", "WLC")}</label>
                <ColorPicker
                  color={darkModeTextColor}
                  onChangeComplete={(color) =>
                    handleColorChange(color, "darkModeTextColor")
                  }
                  disableAlpha
                />
              </div>
            </>
          )}
          <div>
            <label>{__("Button Text Color", "WLC")}</label>
            <ColorPicker
              color={buttonTextColor}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonTextColor")
              }
              disableAlpha
            />
          </div>
          <div>
            <label>{__("Button Background Color", "WLC")}</label>
            <ColorPicker
              color={buttonBackgroundColor}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonBackgroundColor")
              }
              disableAlpha
            />
          </div>
          <div>
            <label>{__("Button Text Hover Color", "WLC")}</label>
            <ColorPicker
              color={buttonTextColorOnHover}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonTextColorOnHover")
              }
              disableAlpha
            />
          </div>
          <div>
            <label>{__("Button Background Hover Color", "WLC")}</label>
            <ColorPicker
              color={buttonBackgroundColorOnHover}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonBackgroundColorOnHover")
              }
              disableAlpha
            />
          </div>
        </PanelBody>
      </InspectorControls>

      <div {...blockProps}>
        <div
          className="pricing-block__top-section"
          style={{
            color: isDarkMode ? darkModeTextColor : "inherit",
            backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
          }}
        >
          {imageUrl && <img src={imageUrl} alt="Decorative" />}
          {isFeatured && (
            <RichText
              tagName="div"
              className="pricing-block__featured-label"
              value={featuredLabel}
              onChange={(value) => setAttributes({ featuredLabel: value })}
            />
          )}
          {title && (
            <RichText
              tagName="p"
              className="pricing-block__title"
              value={title}
              onChange={(value) => setAttributes({ title: value })}
            />
          )}
          {description && (
            <RichText
              tagName="p"
              className="pricing-block__description"
              value={description}
              onChange={(value) => setAttributes({ description: value })}
            />
          )}
          <div className="pricing-block__currency">
            {currencyPosition === "before" && currency && (
              <RichText
                tagName="span"
                className="pricing-block__currency"
                value={currency}
                onChange={(value) => setAttributes({ currency: value })}
              />
            )}
            {price && (
              <RichText
                tagName="span"
                className="pricing-block__price"
                value={price}
                onChange={(value) => setAttributes({ price: value })}
              />
            )}
            {currencyPosition === "after" && currency && (
              <RichText
                tagName="span"
                className="pricing-block__currency"
                value={currency}
                onChange={(value) => setAttributes({ currency: value })}
              />
            )}
            <RichText
              tagName="span"
              className="pricing-block__separator"
              value="/"
              placeholder="/"
            />
            {period && (
              <RichText
                tagName="span"
                className="pricing-block__period"
                value={period}
                onChange={(value) => setAttributes({ period: value })}
              />
            )}
          </div>
          {priceDescription && (
            <RichText
              tagName="p"
              className="pricing-block__price-description"
              value={priceDescription}
              onChange={(value) => setAttributes({ priceDescription: value })}
            />
          )}
          {buttonPosition === "top" && <PricingButton />}
        </div>
        <div className="pricing-block__bottom-section">
          <div className="pricing-block__features-container">
            <ul className="pricing-block__features">
              {features &&
                features.length > 0 &&
                features.map((feature, index) => (
                  <li
                    key={index}
                    className="pricing-block__features__feature-item"
                  >
                    <RichText
                      tagName="span"
                      className="pricing-block__feature-text"
                      value={feature}
                      onChange={(value) => handleFeatureChange(value, index)}
                      placeholder={__("Feature...", "WLC")}
                    />
                    <Button
                      isDestructive
                      onClick={() => handleRemoveFeature(index)}
                      className="pricing-block__features__feature-item__remove-button"
                    >
                      <Icon icon={close} />
                    </Button>
                  </li>
                ))}
            </ul>
            <TextControl
              value={newFeature}
              onChange={(value) => setNewFeature(value)}
              onKeyPress={(e) => {
                if (e.key === "Enter") {
                  handleAddFeature();
                  e.preventDefault();
                }
              }}
              placeholder={__("Enter a feature and press Enter...", "WLC")}
              className="new-feature-input"
            />
            <Button
              isSecondary
              className="add-feature-button"
              onClick={handleAddFeature}
            >
              Add Feature
            </Button>
          </div>
          {buttonPosition === "bottom" && <PricingButton />}
        </div>
      </div>
    </>
  );
};

export default edit;

edit.jsx

import {
  InspectorControls,
  useBlockProps,
  RichText,
  URLInput,
  MediaUpload,
  MediaUploadCheck,
} from "@wordpress/block-editor";
import {
  TextControl,
  PanelBody,
  Button,
  ToggleControl,
  Icon,
  ColorPicker,
  SelectControl,
} from "@wordpress/components";
import { __ } from "@wordpress/i18n";
import { React, useState, useCallback } from "react";
import { close } from "@wordpress/icons";

const MediaUploader = ({ imageUrl, setAttributes }) => {
  const onSelectImage = useCallback(
    (media) => {
      setAttributes({ imageUrl: media.url });
    },
    [setAttributes],
  );

  const getImageName = useCallback((url) => {
    if (!url) return "";
    const parts = url.split("/");
    return parts[parts.length - 1];
  }, []);

  const onRemoveImage = useCallback(() => {
    setAttributes({ imageUrl: "" });
  }, [setAttributes]);

  return (
    <MediaUploadCheck>
      <MediaUpload
        onSelect={onSelectImage}
        allowedTypes={["image"]}
        value={imageUrl}
        render={({ open }) => (
          <div className="components-base-control">
            {imageUrl ? (
              <>
                <div>{__("Uploaded Image", "WLC")}</div>
                <Button onClick={open} isDefault>
                  {getImageName(imageUrl)}
                </Button>
                <Button onClick={onRemoveImage} isDestructive>
                  {__("Remove Image", "WLC")}
                </Button>
              </>
            ) : (
              <Button onClick={open} isDefault>
                {__("Upload Image", "WLC")}
              </Button>
            )}
          </div>
        )}
      />
    </MediaUploadCheck>
  );
};

const PricingButton = ({ buttonUrl, buttonLabel }) =>
  buttonUrl &&
  buttonLabel && (
    <a href={buttonUrl} className="pricing-block__button">
      {buttonLabel}
    </a>
  );

const Edit = ({ attributes, setAttributes }) => {
  const {
    title,
    description,
    currency,
    currencyPosition,
    price,
    features,
    period,
    priceDescription,
    buttonLabel,
    buttonUrl,
    buttonPosition,
    buttonBackgroundColor,
    buttonTextColor,
    buttonTextColorOnHover,
    buttonBackgroundColorOnHover,
    isFeatured,
    featuredLabel,
    isDarkMode,
    darkModeBackgroundColor,
    darkModeTextColor,
    imageUrl,
  } = attributes;

  const blockProps = useBlockProps({
    className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
    style: {
      "--pricing-button-text-color": buttonTextColor,
      "--pricing-button-text-color-on-hover": buttonTextColorOnHover,
      "--pricing-button-bg-color": buttonBackgroundColor,
      "--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
    },
  });

  const [newFeature, setNewFeature] = useState("");

  const handleAddFeature = useCallback(() => {
    if (newFeature.trim() !== "") {
      setAttributes({ features: [...features, newFeature] });
      setNewFeature("");
    }
  }, [newFeature, features, setAttributes]);

  const handleRemoveFeature = useCallback(
    (index) => {
      const updatedFeatures = features.filter((_, i) => i !== index);
      setAttributes({ features: updatedFeatures });
    },
    [features, setAttributes],
  );

  const handleFeatureChange = useCallback(
    (value, index) => {
      const updatedFeatures = [...features];
      updatedFeatures[index] = value;
      setAttributes({ features: updatedFeatures });
    },
    [features, setAttributes],
  );

  const handleColorChange = (color, colorType) => {
    setAttributes({ [colorType]: color.hex });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Pricing Settings", "WLC")}>
          <ToggleControl
            label={__("Featured", "WLC")}
            checked={isFeatured}
            onChange={(value) => setAttributes({ isFeatured: value })}
          />
          {isFeatured && (
            <TextControl
              label={__("Featured Label", "WLC")}
              value={featuredLabel}
              onChange={(value) => setAttributes({ featuredLabel: value })}
              placeholder={__("Enter featured label...", "WLC")}
            />
          )}
          <MediaUploader imageUrl={imageUrl} setAttributes={setAttributes} />
          <TextControl
            label={__("Title", "WLC")}
            value={title}
            onChange={(value) => setAttributes({ title: value })}
          />
          <TextControl
            label={__("Description", "WLC")}
            value={description}
            onChange={(value) => setAttributes({ description: value })}
          />
          <TextControl
            label={__("Currency", "WLC")}
            value={currency}
            onChange={(value) => setAttributes({ currency: value })}
          />
          <SelectControl
            label={__("Currency Position", "WLC")}
            value={currencyPosition}
            options={[
              { label: __("Before price", "WLC"), value: "before" },
              { label: __("After price", "WLC"), value: "after" },
            ]}
            onChange={(value) => setAttributes({ currencyPosition: value })}
          />
          <TextControl
            label={__("Price", "WLC")}
            value={price}
            onChange={(value) => setAttributes({ price: value })}
          />
          <TextControl
            label={__("Period", "WLC")}
            value={period}
            onChange={(value) => setAttributes({ period: value })}
          />
          <TextControl
            label={__("Price description", "WLC")}
            value={priceDescription}
            onChange={(value) => setAttributes({ priceDescription: value })}
          />
          <SelectControl
            label={__("Button Position", "WLC")}
            value={buttonPosition}
            options={[
              { label: __("Bottom Section", "WLC"), value: "bottom" },
              { label: __("Top Section", "WLC"), value: "top" },
            ]}
            onChange={(value) => setAttributes({ buttonPosition: value })}
          />
          <TextControl
            label={__("Button Label", "WLC")}
            value={buttonLabel}
            onChange={(value) => setAttributes({ buttonLabel: value })}
          />
          <URLInput
            label={__("Button URL", "WLC")}
            value={buttonUrl}
            onChange={(value) => setAttributes({ buttonUrl: value })}
          />
        </PanelBody>
      </InspectorControls>

      <InspectorControls group="styles">
        <PanelBody title={__("Pricing Styles", "WLC")}>
          <ToggleControl
            label={__("Dark Mode", "WLC")}
            checked={isDarkMode}
            onChange={(value) => setAttributes({ isDarkMode: value })}
          />
          {isDarkMode && (
            <>
              <div>
                <label htmlFor="darkModeBackgroundColor">
                  {__("Dark Mode Background Color", "WLC")}
                </label>
                <ColorPicker
                  id="darkModeBackgroundColor"
                  color={darkModeBackgroundColor}
                  onChangeComplete={(color) =>
                    handleColorChange(color, "darkModeBackgroundColor")
                  }
                  disableAlpha
                />
              </div>
              <div>
                <label htmlFor="darkModeTextColor">
                  {__("Dark Mode Text Color", "WLC")}
                </label>
                <ColorPicker
                  id="darkModeTextColor"
                  color={darkModeTextColor}
                  onChangeComplete={(color) =>
                    handleColorChange(color, "darkModeTextColor")
                  }
                  disableAlpha
                />
              </div>
            </>
          )}
          <div>
            <label htmlFor="buttonTextColor">
              {__("Button Text Color", "WLC")}
            </label>
            <ColorPicker
              id="buttonTextColor"
              color={buttonTextColor}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonTextColor")
              }
              disableAlpha
            />
          </div>
          <div>
            <label htmlFor="buttonBackgroundColor">
              {__("Button Background Color", "WLC")}
            </label>
            <ColorPicker
              id="buttonBackgroundColor"
              color={buttonBackgroundColor}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonBackgroundColor")
              }
              disableAlpha
            />
          </div>
          <div>
            <label htmlFor="buttonTextColorOnHover">
              {__("Button Text Hover Color", "WLC")}
            </label>
            <ColorPicker
              id="buttonTextColorOnHover"
              color={buttonTextColorOnHover}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonTextColorOnHover")
              }
              disableAlpha
            />
          </div>
          <div>
            <label htmlFor="buttonBackgroundColorOnHover">
              {__("Button Background Hover Color", "WLC")}
            </label>
            <ColorPicker
              id="buttonBackgroundColorOnHover"
              color={buttonBackgroundColorOnHover}
              onChangeComplete={(color) =>
                handleColorChange(color, "buttonBackgroundColorOnHover")
              }
              disableAlpha
            />
          </div>
        </PanelBody>
      </InspectorControls>

      <div {...blockProps}>
        <div
          className="pricing-block__top-section"
          style={{
            color: isDarkMode ? darkModeTextColor : "inherit",
            backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
          }}
        >
          {imageUrl && <img src={imageUrl} alt="Decorative" />}
          {isFeatured && (
            <RichText
              tagName="div"
              className="pricing-block__featured-label"
              value={featuredLabel}
              onChange={(value) => setAttributes({ featuredLabel: value })}
            />
          )}
          {title && (
            <RichText
              tagName="p"
              className="pricing-block__title"
              value={title}
              onChange={(value) => setAttributes({ title: value })}
            />
          )}
          {description && (
            <RichText
              tagName="p"
              className="pricing-block__description"
              value={description}
              onChange={(value) => setAttributes({ description: value })}
            />
          )}
          <div className="pricing-block__currency">
            {currencyPosition === "before" && currency && (
              <RichText
                tagName="span"
                className="pricing-block__currency"
                value={currency}
                onChange={(value) => setAttributes({ currency: value })}
              />
            )}
            {price && (
              <RichText
                tagName="span"
                className="pricing-block__price"
                value={price}
                onChange={(value) => setAttributes({ price: value })}
              />
            )}
            {currencyPosition === "after" && currency && (
              <RichText
                tagName="span"
                className="pricing-block__currency"
                value={currency}
                onChange={(value) => setAttributes({ currency: value })}
              />
            )}
            <RichText
              tagName="span"
              className="pricing-block__separator"
              value="/"
              placeholder="/"
            />
            {period && (
              <RichText
                tagName="span"
                className="pricing-block__period"
                value={period}
                onChange={(value) => setAttributes({ period: value })}
              />
            )}
          </div>
          {priceDescription && (
            <RichText
              tagName="p"
              className="pricing-block__price-description"
              value={priceDescription}
              onChange={(value) => setAttributes({ priceDescription: value })}
            />
          )}
          {buttonPosition === "top" && (
            <PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
          )}
        </div>
        <div className="pricing-block__bottom-section">
          <div className="pricing-block__features-container">
            <ul className="pricing-block__features">
              {features &&
                features.length > 0 &&
                features.map((feature, index) => (
                  <li
                    key={`${feature}-${index}`}
                    className="pricing-block__features__feature-item"
                  >
                    <RichText
                      tagName="span"
                      className="pricing-block__feature-text"
                      value={feature}
                      onChange={(value) => handleFeatureChange(value, index)}
                      placeholder={__("Feature...", "WLC")}
                    />
                    <Button
                      isDestructive
                      onClick={() => handleRemoveFeature(index)}
                      className="pricing-block__features__feature-item__remove-button"
                    >
                      <Icon icon={close} />
                    </Button>
                  </li>
                ))}
            </ul>
            <TextControl
              value={newFeature}
              onChange={(value) => setNewFeature(value)}
              onKeyPress={(e) => {
                if (e.key === "Enter") {
                  handleAddFeature();
                  e.preventDefault();
                }
              }}
              placeholder={__("Enter a feature and press Enter...", "WLC")}
              className="new-feature-input"
            />
            <Button
              isSecondary
              className="add-feature-button"
              onClick={handleAddFeature}
            >
              Add Feature
            </Button>
          </div>
          {buttonPosition === "bottom" && (
            <PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
          )}
        </div>
      </div>
    </>
  );
};

export default Edit;

index.js

import { registerBlockType } from "@wordpress/blocks";
import metadata from './block.json';
import save from "./save";
import edit from './edit';
import './style.scss';

registerBlockType( metadata, {
    edit: edit,
    save: save,
});

save.js

import { RichText, useBlockProps } from "@wordpress/block-editor";
import { React } from "react";

const PricingButton = ({ buttonUrl, buttonLabel }) =>
  buttonUrl &&
  buttonLabel && (
    <a href={buttonUrl} className="pricing-block__button">
      {buttonLabel}
    </a>
  );
const save = ({ attributes }) => {
  const {
    title,
    description,
    currency,
    currencyPosition,
    price,
    features,
    period,
    priceDescription,
    buttonLabel,
    buttonUrl,
    buttonPosition,
    buttonBackgroundColor,
    buttonTextColor,
    buttonTextColorOnHover,
    buttonBackgroundColorOnHover,
    isFeatured,
    featuredLabel,
    isDarkMode,
    darkModeBackgroundColor,
    darkModeTextColor,
    imageUrl,
  } = attributes;

  const blockProps = useBlockProps.save({
    className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
    style: {
      "--pricing-button-text-color": buttonTextColor,
      "--pricing-button-text-color-on-hover": buttonTextColorOnHover,
      "--pricing-button-bg-color": buttonBackgroundColor,
      "--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
    },
  });

  const getStyles = () => {
    return {
      topSection: {
        color: isDarkMode ? darkModeTextColor : "inherit",
        backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
      },
      button: {
        color: buttonTextColor,
        backgroundColor: buttonBackgroundColor,
      },
    };
  };

  const styles = getStyles();

  return (
    <div {...blockProps}>
      <div className="pricing-block__top-section" style={styles.topSection}>
        {imageUrl && <img src={imageUrl} alt="Decorative" />}
        {isFeatured && (
          <RichText.Content
            tagName="div"
            className="pricing-block__featured-label"
            value={featuredLabel}
          />
        )}
        <RichText.Content
          tagName="p"
          className="pricing-block__title"
          value={title}
        />
        <RichText.Content
          tagName="p"
          className="pricing-block__description"
          value={description}
        />
        <div className="pricing-block__currency">
          {currencyPosition === "before" && (
            <RichText.Content
              tagName="span"
              className="pricing-block__currency"
              value={currency}
            />
          )}
          <RichText.Content
            tagName="span"
            className="pricing-block__price"
            value={price}
          />
          {currencyPosition === "after" && (
            <RichText.Content
              tagName="span"
              className="pricing-block__currency"
              value={currency}
            />
          )}
          <RichText.Content
            tagName="span"
            className="pricing-block__separator"
            value="/"
          />
          <RichText.Content
            tagName="span"
            className="pricing-block__period"
            value={period}
          />
        </div>
        <RichText.Content
          tagName="p"
          className="pricing-block__price-description"
          value={priceDescription}
        />
        {buttonPosition === "top" && (
          <PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
        )}
      </div>
      <div className="pricing-block__bottom-section">
        {features && features.length > 0 && (
          <ul className="pricing-block__features">
            {features.map((feature, index) => (
              <li
                key={`${feature}-${index}`}
                className="pricing-block__features__feature-item"
              >
                {feature}
              </li>
            ))}
          </ul>
        )}
        {buttonPosition === "bottom" && (
          <PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
        )}
      </div>
    </div>
  );
};

export default save;

style.scss

.pricing-block {
  @apply relative border border-grey-300 rounded-lg bg-white shadow-md;

  &.is-featured {
    @apply border-blue-500 bg-blue-50;
  }

  &.dark-mode {
    .pricing-block__top-section {
      @apply p-6 pb-6;
    }

    .pricing-block__bottom-section {
      @apply pt-6;
    }
  }

  &__top-section {
    @apply p-6 pb-2;
    img {
      @apply absolute top-4 right-4 w-16 h-16 object-cover;
    }
  }

  &__bottom-section {
    @apply p-6 pt-1;

    &.dark-mode & {
      @apply pt-6;
    }
  }

  &__featured-label {
    @apply absolute -top-4 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white px-4 py-1 rounded-lg text-16 font-bold;
  }

  &__title {
    @apply text-20 font-bold mb-2;
  }

  &__description {
    @apply text-18 mb-4;
  }

  &__currency {
    @apply text-60 font-bold;
  }

  &__price {
    @apply mb-4;
  }

  &__separator {
    @apply text-24 mb-1 font-bold;
  }

  &__period {
    @apply text-24 mb-1 font-bold;
  }

  &__price-description {
    @apply text-18;
  }

  &__features {
    @apply mb-1 mt-1;

    &__feature-item {
      @apply flex pb-2 items-center;

      &::before {
        @apply content-icon-tick w-8 h-6;
      }

      &__feature-text {
        @apply mr-2;
      }

      &__remove-button {
        @apply p-0;
      }
    }
  }

  &__button {
    @apply block text-center bg-black text-white py-2 px-4 mt-3 rounded-md transition duration-200;
    @apply border border-solid;
    color: var(--pricing-button-text-color);
    background-color: var(--pricing-button-bg-color);

    &:hover {
      @apply border border-solid;
      color: var(--pricing-button-text-color-on-hover);
      background-color: var(--pricing-button-bg-color-on-hover);
      border-color: var(--pricing-button-text-color-on-hover);
    }
  }
}

.add-feature-button {
  @apply text-white py-2 px-4 rounded-md hover:bg-blue-300 focus:outline-none mb-4;
}

.new-feature-input {
  @apply w-full mb-4 mt-1;
}

view.js

document.addEventListener( 'DOMContentLoaded', function() {
	// add js to be served on frontend only
} );