PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.0.8
AI Builder – Generate pages, blocks, images & translate with AI v2.0.8
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / src / editor-blocks / cards / index.js

index.js in AI Builder – Generate pages, blocks, images & translate with AI 2.0.8, at assets/js/src/editor-blocks/cards/index.js

866 lines 26.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 const { registerBlockType } = wp.blocks;
2 const { __ } = wp.i18n;
3 const {
4 PanelBody,
5 TextControl,
6 TextareaControl,
7 Button,
8 Notice,
9 ToggleControl,
10 RangeControl,
11 } = wp.components;
12 const { InspectorControls, useBlockProps, MediaUpload, MediaUploadCheck } =
13 wp.blockEditor || wp.editor;
14
15 // Use HTML5 color input instead of WordPress ColorPicker to avoid compatibility issues
16 const SafeColorPicker = ({ color, onChange, disableAlpha }) => (
17 <div style={{ position: "relative" }}>
18 <input
19 type="color"
20 value={color || "#4f46e5"}
21 onChange={(e) => onChange && onChange(e.target.value)}
22 style={{
23 width: "100%",
24 height: "40px",
25 border: "1px solid #ddd",
26 borderRadius: "4px",
27 cursor: "pointer",
28 }}
29 />
30 </div>
31 );
32
33 // WordPress MediaUpload component wrapper
34 const ImageSelector = ({ onSelect, imageUrl, imageId, label }) => {
35 const onSelectImage = (media) => {
36 onSelect({
37 url: media.url,
38 id: media.id,
39 });
40 };
41
42 const onRemoveImage = () => {
43 onSelect({
44 url: "",
45 id: "",
46 });
47 };
48
49 return (
50 <div>
51 <div
52 className="aibui-card-image"
53 style={{
54 width: "100%",
55 height: "120px",
56 backgroundImage: imageUrl ? `url(${imageUrl})` : "none",
57 backgroundSize: "cover",
58 backgroundPosition: "center",
59 borderRadius: "6px",
60 marginBottom: "8px",
61 border: "1px solid #e0e0e0",
62 }}
63 />
64 <div style={{ display: "flex", gap: "8px" }}>
65 <Button
66 onClick={() => {
67 if (typeof wp === "undefined" || !wp.media) {
68 alert(
69 __(
70 "WordPress media library is not available. Please refresh the page.",
71 "ai-builder"
72 )
73 );
74 return;
75 }
76
77 const mediaUploader = wp.media({
78 title: __("Select Image", "ai-builder"),
79 button: {
80 text: __("Use This Image", "ai-builder"),
81 },
82 multiple: false,
83 library: {
84 type: "image",
85 },
86 });
87
88 mediaUploader.on("select", function () {
89 const attachment = mediaUploader
90 .state()
91 .get("selection")
92 .first()
93 .toJSON();
94 console.log("Selected attachment:", attachment);
95
96 if (attachment && attachment.url) {
97 onSelectImage(attachment);
98 } else {
99 console.error(
100 "No attachment selected or attachment missing URL"
101 );
102 }
103 });
104
105 mediaUploader.open();
106 }}
107 isSecondary
108 >
109 {imageUrl
110 ? __("Change Image", "ai-builder")
111 : __("Select Image", "ai-builder")}
112 </Button>
113 {imageUrl && (
114 <Button onClick={onRemoveImage} isDestructive isSmall>
115 {__("Remove", "ai-builder")}
116 </Button>
117 )}
118 </div>
119 </div>
120 );
121 };
122
123 registerBlockType("ai-builder/aibui-cards", {
124 title: __("Product Cards (AI Builder)", "ai-builder"),
125 icon: "grid-view",
126 category: "widgets",
127 supports: { align: ["wide", "full"], html: false },
128 attributes: {
129 cards: {
130 type: "array",
131 default: [
132 {
133 id: "card1",
134 title: "Premium Product",
135 description:
136 "High-quality product with excellent features and great value for money.",
137 price: "$99.99",
138 originalPrice: "$149.99",
139 imageUrl: "",
140 imageId: "",
141 buttonText: "Buy Now",
142 buttonLink: "",
143 buttonColor: "#4f46e5",
144 buttonTextColor: "#ffffff",
145 badge: "Sale",
146 badgeColor: "#ef4444",
147 },
148 ],
149 },
150 columns: { type: "number", default: 3 },
151 cardSpacing: { type: "number", default: 20 },
152 cardBackground: { type: "string", default: "#ffffff" },
153 cardBorderRadius: { type: "number", default: 12 },
154 showBadges: { type: "boolean", default: true },
155 defaultImageUrl: { type: "string", default: "" },
156 defaultImageId: { type: "number", default: 0 },
157 },
158 edit: (props) => {
159 const { attributes, setAttributes } = props;
160 const {
161 cards,
162 columns,
163 cardSpacing,
164 cardBackground,
165 cardBorderRadius,
166 showBadges,
167 defaultImageUrl,
168 defaultImageId,
169 } = attributes;
170 const blockProps = useBlockProps({ className: "aibui-cards" });
171
172 const addCard = () => {
173 const newCard = {
174 id: `card${Date.now()}`,
175 title: "New Product",
176 description: "Product description here.",
177 price: "$49.99",
178 originalPrice: "",
179 imageUrl: defaultImageUrl || "",
180 imageId: defaultImageId || "",
181 buttonText: "Buy Now",
182 buttonLink: "",
183 buttonColor: "#4f46e5",
184 buttonTextColor: "#ffffff",
185 badge: "New",
186 badgeColor: "#10b981",
187 };
188 setAttributes({
189 cards: [...cards, newCard],
190 });
191 };
192
193 const removeCard = (cardId) => {
194 setAttributes({
195 cards: cards.filter((card) => card.id !== cardId),
196 });
197 };
198
199 const updateCard = (cardId, field, value) => {
200 setAttributes({
201 cards: cards.map((card) =>
202 card.id === cardId ? { ...card, [field]: value } : card
203 ),
204 });
205 };
206
207 const applyDefaultImageToAll = () => {
208 if (!defaultImageUrl) return;
209 setAttributes({
210 cards: cards.map((card) =>
211 !card.imageUrl
212 ? { ...card, imageUrl: defaultImageUrl, imageId: defaultImageId }
213 : card
214 ),
215 });
216 };
217
218 return (
219 <div {...blockProps}>
220 <InspectorControls>
221 <PanelBody title={__("Cards Settings", "ai-builder")} initialOpen>
222 <RangeControl
223 label={__("Number of columns", "ai-builder")}
224 value={columns}
225 onChange={(val) => setAttributes({ columns: val || 3 })}
226 min={1}
227 max={4}
228 />
229
230 <RangeControl
231 label={__("Card spacing (px)", "ai-builder")}
232 value={cardSpacing}
233 onChange={(val) => setAttributes({ cardSpacing: val || 20 })}
234 min={10}
235 max={40}
236 />
237
238 <div style={{ marginTop: "12px" }}>
239 <p style={{ margin: "0 0 8px" }}>
240 {__("Card background color", "ai-builder")}
241 </p>
242 <SafeColorPicker
243 color={cardBackground}
244 onChange={(val) =>
245 setAttributes({ cardBackground: val || "#ffffff" })
246 }
247 />
248 </div>
249
250 <RangeControl
251 label={__("Card border radius (px)", "ai-builder")}
252 value={cardBorderRadius}
253 onChange={(val) => setAttributes({ cardBorderRadius: val || 12 })}
254 min={0}
255 max={30}
256 />
257
258 <ToggleControl
259 label={__("Show badges", "ai-builder")}
260 checked={showBadges}
261 onChange={(val) => setAttributes({ showBadges: !!val })}
262 />
263
264 <div
265 style={{
266 marginTop: "20px",
267 paddingTop: "20px",
268 borderTop: "1px solid #e0e0e0",
269 }}
270 >
271 <h4 style={{ margin: "0 0 12px 0" }}>
272 {__("Default Image", "ai-builder")}
273 </h4>
274 <p
275 style={{
276 margin: "0 0 12px 0",
277 fontSize: "13px",
278 color: "#666",
279 }}
280 >
281 {__(
282 "Set a default image for cards without images",
283 "ai-builder"
284 )}
285 </p>
286 <ImageSelector
287 onSelect={(media) => {
288 setAttributes({
289 defaultImageUrl: media.url,
290 defaultImageId: media.id,
291 });
292 }}
293 imageUrl={defaultImageUrl}
294 imageId={defaultImageId}
295 label={__("Default Image", "ai-builder")}
296 />
297 {defaultImageUrl && (
298 <Button
299 onClick={applyDefaultImageToAll}
300 isPrimary
301 isSmall
302 style={{ marginTop: "8px" }}
303 >
304 {__("Apply to Empty Cards", "ai-builder")}
305 </Button>
306 )}
307 </div>
308
309 <div style={{ marginTop: "16px" }}>
310 <h4>{__("Cards", "ai-builder")}</h4>
311 {cards.map((card, index) => (
312 <div
313 key={card.id}
314 style={{
315 border: "1px solid #e0e0e0",
316 borderRadius: "8px",
317 padding: "12px",
318 marginBottom: "12px",
319 backgroundColor: "#f9f9f9",
320 }}
321 >
322 <div
323 style={{
324 display: "flex",
325 justifyContent: "space-between",
326 alignItems: "center",
327 marginBottom: "8px",
328 }}
329 >
330 <strong>
331 {__("Card", "ai-builder")} {index + 1}
332 </strong>
333 <Button
334 isDestructive
335 isSmall
336 onClick={() => removeCard(card.id)}
337 >
338 {__("Remove", "ai-builder")}
339 </Button>
340 </div>
341
342 <TextControl
343 label={__("Product title", "ai-builder")}
344 value={card.title}
345 onChange={(val) => updateCard(card.id, "title", val || "")}
346 />
347
348 <TextareaControl
349 label={__("Description", "ai-builder")}
350 value={card.description}
351 onChange={(val) =>
352 updateCard(card.id, "description", val || "")
353 }
354 rows={3}
355 />
356
357 <div style={{ display: "flex", gap: "12px" }}>
358 <TextControl
359 label={__("Price", "ai-builder")}
360 value={card.price}
361 onChange={(val) =>
362 updateCard(card.id, "price", val || "")
363 }
364 style={{ flex: 1 }}
365 />
366 <TextControl
367 label={__("Original price (optional)", "ai-builder")}
368 value={card.originalPrice}
369 onChange={(val) =>
370 updateCard(card.id, "originalPrice", val || "")
371 }
372 style={{ flex: 1 }}
373 />
374 </div>
375
376 <div style={{ marginBottom: "16px" }}>
377 <p style={{ margin: "0 0 8px", fontWeight: "500" }}>
378 {__("Product Image", "ai-builder")}
379 </p>
380 <ImageSelector
381 onSelect={(media) => {
382 console.log("Updating card with media:", media);
383 console.log("Card ID:", card.id);
384 console.log("Current card:", card);
385
386 // Update both fields in a single setAttributes call
387 setAttributes({
388 cards: cards.map((c) =>
389 c.id === card.id
390 ? { ...c, imageUrl: media.url, imageId: media.id }
391 : c
392 ),
393 });
394
395 console.log(
396 "Updated cards:",
397 cards.map((c) =>
398 c.id === card.id
399 ? { ...c, imageUrl: media.url, imageId: media.id }
400 : c
401 )
402 );
403 }}
404 imageUrl={card.imageUrl}
405 imageId={card.imageId}
406 label={__("Product Image", "ai-builder")}
407 />
408 </div>
409
410 <div style={{ display: "flex", gap: "12px" }}>
411 <TextControl
412 label={__("Button text", "ai-builder")}
413 value={card.buttonText}
414 onChange={(val) =>
415 updateCard(card.id, "buttonText", val || "")
416 }
417 style={{ flex: 1 }}
418 />
419 <TextControl
420 label={__("Button link", "ai-builder")}
421 value={card.buttonLink}
422 onChange={(val) =>
423 updateCard(card.id, "buttonLink", val || "")
424 }
425 style={{ flex: 1 }}
426 />
427 </div>
428
429 <div
430 style={{ display: "flex", gap: "12px", marginTop: "12px" }}
431 >
432 <div style={{ flex: 1 }}>
433 <p style={{ margin: "0 0 8px" }}>
434 {__("Button color", "ai-builder")}
435 </p>
436 <SafeColorPicker
437 color={card.buttonColor}
438 onChange={(val) =>
439 updateCard(card.id, "buttonColor", val || "#4f46e5")
440 }
441 />
442 </div>
443 <div style={{ flex: 1 }}>
444 <p style={{ margin: "0 0 8px" }}>
445 {__("Button text color", "ai-builder")}
446 </p>
447 <SafeColorPicker
448 color={card.buttonTextColor}
449 onChange={(val) =>
450 updateCard(
451 card.id,
452 "buttonTextColor",
453 val || "#ffffff"
454 )
455 }
456 />
457 </div>
458 </div>
459
460 <div
461 style={{ display: "flex", gap: "12px", marginTop: "12px" }}
462 >
463 <TextControl
464 label={__("Badge text", "ai-builder")}
465 value={card.badge}
466 onChange={(val) =>
467 updateCard(card.id, "badge", val || "")
468 }
469 style={{ flex: 1 }}
470 />
471 <div style={{ flex: 1 }}>
472 <p style={{ margin: "0 0 8px" }}>
473 {__("Badge color", "ai-builder")}
474 </p>
475 <SafeColorPicker
476 color={card.badgeColor}
477 onChange={(val) =>
478 updateCard(card.id, "badgeColor", val || "#ef4444")
479 }
480 />
481 </div>
482 </div>
483 </div>
484 ))}
485
486 <Button isPrimary onClick={addCard}>
487 {__("Add Card", "ai-builder")}
488 </Button>
489 </div>
490 </PanelBody>
491 </InspectorControls>
492
493 <CardsPreview
494 cards={cards}
495 columns={columns}
496 cardSpacing={cardSpacing}
497 cardBackground={cardBackground}
498 cardBorderRadius={cardBorderRadius}
499 showBadges={showBadges}
500 defaultImageUrl={defaultImageUrl}
501 defaultImageId={defaultImageId}
502 />
503 </div>
504 );
505 },
506 save: (props) => {
507 const { attributes } = props;
508 const {
509 cards,
510 columns,
511 cardSpacing,
512 cardBackground,
513 cardBorderRadius,
514 showBadges,
515 defaultImageUrl,
516 defaultImageId,
517 } = attributes;
518 const blockProps = wp.blockEditor
519 ? wp.blockEditor.useBlockProps.save({ className: "aibui-cards" })
520 : {};
521
522 return (
523 <div {...blockProps}>
524 <div
525 className="aibui-cards-container"
526 style={{
527 display: "grid",
528 gridTemplateColumns: `repeat(${columns}, 1fr)`,
529 gap: `${cardSpacing}px`,
530 maxWidth: "1200px",
531 margin: "0 auto",
532 }}
533 >
534 {cards.map((card) => (
535 <div
536 key={card.id}
537 className="aibui-single-card"
538 style={{
539 backgroundColor: cardBackground,
540 borderRadius: `${cardBorderRadius}px`,
541 padding: "20px",
542 boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
543 border: "1px solid #e5e7eb",
544 position: "relative",
545 transition: "transform 0.2s ease, box-shadow 0.2s ease",
546 }}
547 >
548 {showBadges && card.badge && (
549 <div
550 className="aibui-card-badge"
551 style={{
552 position: "absolute",
553 top: "12px",
554 right: "12px",
555 backgroundColor: card.badgeColor,
556 color: "#ffffff",
557 padding: "4px 8px",
558 borderRadius: "4px",
559 fontSize: "12px",
560 fontWeight: "600",
561 textTransform: "uppercase",
562 }}
563 >
564 {card.badge}
565 </div>
566 )}
567
568 {(card.imageUrl || defaultImageUrl) && (
569 <div
570 className="aibui-card-image"
571 style={{
572 width: "100%",
573 height: "200px",
574 backgroundImage: `url(${card.imageUrl || defaultImageUrl})`,
575 backgroundSize: "cover",
576 backgroundPosition: "center",
577 borderRadius: "8px",
578 marginBottom: "16px",
579 }}
580 />
581 )}
582
583 <h3
584 className="aibui-card-title"
585 style={{
586 fontSize: "18px",
587 fontWeight: "600",
588 margin: "0 0 8px 0",
589 color: "#111827",
590 }}
591 >
592 {card.title}
593 </h3>
594
595 <p
596 className="aibui-card-description"
597 style={{
598 fontSize: "14px",
599 color: "#6b7280",
600 margin: "0 0 16px 0",
601 lineHeight: "1.5",
602 }}
603 >
604 {card.description}
605 </p>
606
607 <div
608 className="aibui-card-pricing"
609 style={{
610 display: "flex",
611 alignItems: "center",
612 gap: "8px",
613 marginBottom: "16px",
614 }}
615 >
616 <span
617 className="aibui-card-price"
618 style={{
619 fontSize: "20px",
620 fontWeight: "700",
621 color: "#111827",
622 }}
623 >
624 {card.price}
625 </span>
626 {card.originalPrice && (
627 <span
628 className="aibui-card-original-price"
629 style={{
630 fontSize: "14px",
631 color: "#9ca3af",
632 textDecoration: "line-through",
633 }}
634 >
635 {card.originalPrice}
636 </span>
637 )}
638 </div>
639
640 {card.buttonLink ? (
641 <a
642 href={card.buttonLink}
643 target="_blank"
644 rel="noopener noreferrer"
645 className="aibui-card-button"
646 style={{
647 display: "block",
648 width: "100%",
649 backgroundColor: card.buttonColor,
650 color: card.buttonTextColor,
651 padding: "12px 16px",
652 borderRadius: "8px",
653 textAlign: "center",
654 textDecoration: "none",
655 fontSize: "14px",
656 fontWeight: "600",
657 transition: "opacity 0.2s ease",
658 boxSizing: "border-box",
659 }}
660 >
661 {card.buttonText}
662 </a>
663 ) : (
664 <button
665 className="aibui-card-button"
666 style={{
667 width: "100%",
668 backgroundColor: card.buttonColor,
669 color: card.buttonTextColor,
670 padding: "12px 16px",
671 borderRadius: "8px",
672 border: "none",
673 fontSize: "14px",
674 fontWeight: "600",
675 cursor: "pointer",
676 transition: "opacity 0.2s ease",
677 boxSizing: "border-box",
678 }}
679 >
680 {card.buttonText}
681 </button>
682 )}
683 </div>
684 ))}
685 </div>
686 </div>
687 );
688 },
689 });
690
691 function CardsPreview({
692 cards,
693 columns,
694 cardSpacing,
695 cardBackground,
696 cardBorderRadius,
697 showBadges,
698 defaultImageUrl,
699 defaultImageId,
700 }) {
701 return (
702 <div
703 className="aibui-cards-container"
704 style={{
705 display: "grid",
706 gridTemplateColumns: `repeat(${columns}, 1fr)`,
707 gap: `${cardSpacing}px`,
708 maxWidth: "1200px",
709 margin: "0 auto",
710 }}
711 >
712 {cards.map((card) => (
713 <div
714 key={card.id}
715 className="aibui-single-card"
716 style={{
717 backgroundColor: cardBackground,
718 borderRadius: `${cardBorderRadius}px`,
719 padding: "20px",
720 boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
721 border: "1px solid #e5e7eb",
722 position: "relative",
723 transition: "transform 0.2s ease, box-shadow 0.2s ease",
724 }}
725 >
726 {showBadges && card.badge && (
727 <div
728 className="aibui-card-badge"
729 style={{
730 position: "absolute",
731 top: "12px",
732 right: "12px",
733 backgroundColor: card.badgeColor,
734 color: "#ffffff",
735 padding: "4px 8px",
736 borderRadius: "4px",
737 fontSize: "12px",
738 fontWeight: "600",
739 textTransform: "uppercase",
740 }}
741 >
742 {card.badge}
743 </div>
744 )}
745
746 {(card.imageUrl || defaultImageUrl) && (
747 <div
748 className="aibui-card-image"
749 style={{
750 width: "100%",
751 height: "200px",
752 backgroundImage: `url(${card.imageUrl || defaultImageUrl})`,
753 backgroundSize: "cover",
754 backgroundPosition: "center",
755 borderRadius: "8px",
756 marginBottom: "16px",
757 }}
758 />
759 )}
760
761 <h3
762 className="aibui-card-title"
763 style={{
764 fontSize: "18px",
765 fontWeight: "600",
766 margin: "0 0 8px 0",
767 color: "#111827",
768 }}
769 >
770 {card.title}
771 </h3>
772
773 <p
774 className="aibui-card-description"
775 style={{
776 fontSize: "14px",
777 color: "#6b7280",
778 margin: "0 0 16px 0",
779 lineHeight: "1.5",
780 }}
781 >
782 {card.description}
783 </p>
784
785 <div
786 className="aibui-card-pricing"
787 style={{
788 display: "flex",
789 alignItems: "center",
790 gap: "8px",
791 marginBottom: "16px",
792 }}
793 >
794 <span
795 className="aibui-card-price"
796 style={{
797 fontSize: "20px",
798 fontWeight: "700",
799 color: "#111827",
800 }}
801 >
802 {card.price}
803 </span>
804 {card.originalPrice && (
805 <span
806 className="aibui-card-original-price"
807 style={{
808 fontSize: "14px",
809 color: "#9ca3af",
810 textDecoration: "line-through",
811 }}
812 >
813 {card.originalPrice}
814 </span>
815 )}
816 </div>
817
818 {card.buttonLink ? (
819 <a
820 href={card.buttonLink}
821 target="_blank"
822 rel="noopener noreferrer"
823 className="aibui-card-button"
824 style={{
825 display: "block",
826 width: "100%",
827 backgroundColor: card.buttonColor,
828 color: card.buttonTextColor,
829 padding: "12px 16px",
830 borderRadius: "8px",
831 textAlign: "center",
832 textDecoration: "none",
833 fontSize: "14px",
834 fontWeight: "600",
835 transition: "opacity 0.2s ease",
836 boxSizing: "border-box",
837 }}
838 >
839 {card.buttonText}
840 </a>
841 ) : (
842 <button
843 className="aibui-card-button"
844 style={{
845 width: "100%",
846 backgroundColor: card.buttonColor,
847 color: card.buttonTextColor,
848 padding: "12px 16px",
849 borderRadius: "8px",
850 border: "none",
851 fontSize: "14px",
852 fontWeight: "600",
853 cursor: "pointer",
854 transition: "opacity 0.2s ease",
855 boxSizing: "border-box",
856 }}
857 >
858 {card.buttonText}
859 </button>
860 )}
861 </div>
862 ))}
863 </div>
864 );
865 }
866