import ReadyPatterns from "./readyPatterns"; import ErrorBoundary from "./ErrorBoundary"; import { __ } from "@wordpress/i18n"; import { CloseIcon } from "./icons"; import { API_ENDPOINTS, KEYBOARD_KEYS } from "./constants"; import { parse } from "@wordpress/blocks"; import { Fragment, useState, useEffect } from "@wordpress/element"; import { SmartPostShowLogoIcon } from "../icons/icons"; // Default attribute values const DEFAULT_ATTRIBUTES = { postType: "multiple_post_type", multiplePostType: [ { id: 1, label: "Posts", value: "post", }, ], quickQuery: "", postLimit: "8", offset: 0, filterByAuthor: [], filterByDate: "", filterByKeyword: "", filterByCustomFields: [], orderBy: "date", orderDirection: "DESC", excludePost: [], excludeTerm: [], excludeAuthor: [], excludeStickyPosts: false, excludeCurrentPosts: true, excludeProtectedPosts: false, excludeChildrenPosts: false, excludePostWithoutImagePosts: false, filterProduct: "recent", relation: "AND", specificMonth: "1", specificYear: "2024", blockName: "", displayAdvertisement: false, liveSearchText: "", postQuery: "", taxonomies: [ { id: 1, type: "", value: [], operator: "IN", initialOpen: true, }, ], categories: [], termId: "", keywordSearch: "", currentPage: 1, itemsPerPage: 6, page_id: "", // Get current date/time in format: Y-m-d H:i:s getCurrentDate: () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }, }; /** * Modify block attributes after parsing but before insertion * This function recursively processes blocks and their innerBlocks * to reset query-related attributes to their default values * * @param {Array} blocks - Array of parsed block objects * @returns {Array} - Modified blocks array */ const modifyBlockAttributes = (blocks) => { if (!Array.isArray(blocks)) { return blocks; } return blocks.map((block) => { // Create a new block object to avoid mutating the original const modifiedBlock = { ...block, attributes: { ...block.attributes, }, }; // Only modify Smart Post Show blocks if (block.name && block.name.startsWith("sp-smart-post-show/") && block.attributes) { // Reset all query-related attributes to default values if they exist const attributesToReset = [ "postType", "multiplePostType", "quickQuery", "postLimit", "offset", "filterByAuthor", "filterByDate", "filterByKeyword", "filterByCustomFields", "orderBy", "orderDirection", "excludePost", "excludeTerm", "excludeAuthor", "excludeStickyPosts", "excludeCurrentPosts", "excludeProtectedPosts", "excludeChildrenPosts", "excludePostWithoutImagePosts", "filterProduct", "relation", "specificMonth", "specificYear", "blockName", "displayAdvertisement", "liveSearchText", "postQuery", "taxonomies", "categories", "termId", "keywordSearch", "currentPage", "itemsPerPage", "page_id", ]; attributesToReset.forEach((attrName) => { if (modifiedBlock.attributes.hasOwnProperty(attrName)) { // Get default value const defaultValue = DEFAULT_ATTRIBUTES[attrName]; // Handle array defaults (deep clone) if (Array.isArray(defaultValue)) { modifiedBlock.attributes[attrName] = JSON.parse(JSON.stringify(defaultValue)); } else { modifiedBlock.attributes[attrName] = defaultValue; } } }); // Reset date fields to current date/time if they exist const dateFields = [ "specificDate", "specificPeriodAfter", "specificPeriodBefore", "specificDateBefore", "specificDateAfter", "excludeDateAfter", "excludeDateBefore", ]; const currentDate = DEFAULT_ATTRIBUTES.getCurrentDate(); dateFields.forEach((fieldName) => { if (modifiedBlock.attributes.hasOwnProperty(fieldName)) { modifiedBlock.attributes[fieldName] = currentDate; } }); } // Recursively process innerBlocks if (block.innerBlocks && Array.isArray(block.innerBlocks) && block.innerBlocks.length > 0) { modifiedBlock.innerBlocks = modifyBlockAttributes(block.innerBlocks); } return modifiedBlock; }); }; // Header Component. const LibraryHeader = ({ onClose, tabState, setTabState }) => { return (
{__("Smart Design Library", "post-carousel")}
setTabState("ready-pattern")}> {__("Ready Patterns", "post-carousel")}
); }; // Main Library Component. const Library = (props) => { const [wishListArr, setWishlistArr] = useState([]); const [tabState, setTabState] = useState("ready-pattern"); const isBlockPattern = props.currentBlockName ? true : false; let currentBlockName = props.currentBlockName || "all"; // let currentSitesName = props.currentSitesName || "all"; if (isBlockPattern && props.currentBlockName === "post-thumbnail-slider") { currentBlockName = "thumbnail-slider"; } const [state, setState] = useState({ isPopup: props.isShow || false, designs: [], reloadId: "", reload: false, error: false, fetching: false, designFilter: currentBlockName || "all", current: [], sidebarOpen: true, templatekitCol: "sp-smart-pattern-col3", loading: false, sitesDesign: [], sitesCurrent: [], sitesCategory: [], }); const { isPopup, designFilter } = state; const localizedData = sp_smart_post_block_localize || {}; // Transform nested category data to flat array. const handleDesignData = (data) => { const transformedData = []; for (const category in data) { const items = data[category]; if ( Array.isArray( items ) ) { items.forEach( item => { transformedData.push({ ...item, category, }) }) } else if ( typeof items === "object" ) { transformedData.push({ ...items, }); } } return transformedData; }; // Fetch Pre-made pattern data from Smart Post Show REST API. const fetchTemplates = async () => { setState((prev) => ({ ...prev, loading: true, error: false })); try { const response = await wp.apiFetch({ path: API_ENDPOINTS.PATTERNS, method: "POST", data: { type: "get_data" }, }); if (!response) { throw new Error(__("No response received from server", "post-carousel")); } if (response.success && response.data) { const allDesignData = JSON.parse(response.data); let designData = handleDesignData(allDesignData); if (isBlockPattern && currentBlockName) { designData = allDesignData[currentBlockName] || []; } setState((prev) => ({ ...prev, current: designData, designs: designData, loading: false, error: false, })); } else { throw new Error(response.message || __("Failed to load patterns", "post-carousel")); } } catch (error) { console.error("Error fetching templates:", error); setState((prev) => ({ ...prev, loading: false, error: error.message || __("An unexpected error occurred", "post-carousel"), })); } }; // Fetch Pre-made sites data from Smart Post Show REST API. const fetchSitesData = async () => { setState((prev) => ({ ...prev, loading: true, error: false })); try { const response = await wp.apiFetch({ path: API_ENDPOINTS.SITES, method: "POST", // data: { type: "get_data" }, }); if (!response) { throw new Error(__("No response received from server", "post-carousel")); } if (response.success && response.data) { const allDesignData = JSON.parse(response.data); const sitesData = handleDesignData(allDesignData); // if (isBlockPattern && currentBlockName) { // sitesData = allDesignData[currentSitesName] || []; // } setState((prev) => ({ ...prev, sitesCurrent: sitesData, sitesDesign: sitesData, loading: false, error: false, })); } else { throw new Error(response.message || __("Failed to load patterns", "post-carousel")); } } catch (error) { console.error("Error fetching templates:", error); setState((prev) => ({ ...prev, loading: false, error: error.message || __("An unexpected error occurred", "post-carousel"), })); } }; // Force fetch and refresh local JSON cache const fetchAllData = async () => { setState((prev) => ({ ...prev, fetching: true }));; try { const response = await wp.apiFetch({ path: API_ENDPOINTS.PATTERNS, method: "POST", data: { type: "refresh" }, // tells PHP to re-fetch from remote }); if (!response) { throw new Error(__("No response received from server", "post-carousel")); } if (response.success) { // after successful refresh, reload data await fetchTemplates(); await fetchSitesData(); } else { throw new Error(response.message || __("Failed to refresh patterns", "post-carousel")); } } catch (error) { console.error("Error fetching all data:", error); setState((prev) => ({ ...prev, error: error.message || __("Failed to refresh data", "post-carousel"), })); } finally { setState((prev) => ({ ...prev, fetching: false })); } }; // Close modal const closeModal = () => { const element = document.querySelector(".sp-smart-builder-modal"); if (element) { element.remove(); } setState((prev) => ({ ...prev, isPopup: false })); }; // Handle ESC key press const handleKeyDown = (e) => { if (e.keyCode === KEYBOARD_KEYS.ESCAPE) { closeModal(); } }; // Insert block into editor const insertBlock = async (templateID) => { if (!templateID) { console.log("Template ID is required"); return; } setState((prev) => ({ ...prev, reload: true, reloadId: templateID })); try { const response = await fetch(API_ENDPOINTS.SINGLE_PATTERN, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ license: "", template_id: templateID, }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const jsonData = await response.json(); if (jsonData.success && jsonData.rawData) { const blockEditor = wp.data.dispatch("core/block-editor"); if (blockEditor && blockEditor.insertBlocks) { // Parse the raw block data - parse() returns an array of blocks let blocks = []; try { blocks = parse(jsonData.rawData); } catch (parseError) { console.error("Error parsing block data:", parseError); throw new Error(__("Failed to parse block data", "post-carousel")); } // Validate that blocks is an array and not empty if (!Array.isArray(blocks)) { console.error("Parsed blocks is not an array:", blocks); throw new Error(__("Invalid block data format", "post-carousel")); } if (blocks.length === 0) { console.warn("No blocks found in parsed data"); throw new Error(__("No blocks found in pattern", "post-carousel")); } // Log parsed blocks for debugging (shows array length and structure) console.log(`Parsed ${blocks.length} block(s):`, blocks); // Modify block attributes after parsing but before insertion // This allows you to change default attributes, reset IDs, etc. blocks = modifyBlockAttributes(blocks); // Log modified blocks for debugging console.log(`Modified ${blocks.length} block(s) before insertion:`, blocks); // Insert all blocks into the editor // insertBlocks accepts an array of block objects blockEditor.insertBlocks(blocks); closeModal(); setState((prev) => ({ ...prev, isPopup: false, reload: false, reloadId: "", error: false, })); } else { throw new Error(__("Block editor is not available", "post-carousel")); } } else { throw new Error(jsonData.message || __("Failed to import pattern", "post-carousel")); } } catch (error) { console.error("Error inserting block:", error); setState((prev) => ({ ...prev, error: error.message || __("Failed to import pattern", "post-carousel"), reload: false, })); } }; // Handle block import const handleBlockImport = (templateID, isPro) => { if (isPro && !localizedData.isPro) { return; } insertBlock(templateID); }; // Split archive data by category key. const filterByCategoryKey = (data = [], key = "") => { // Return early if no data or not an array if (!Array.isArray(data) || data.length === 0) return []; // If key is empty or 'all', return all data if (!key || key === "all") return data; return data.filter((item) => { const category = item?.category; // Handle category as array if (Array.isArray(category)) { const pattern = category.some((cat) => cat?.slug === key) || null; const starter = category.includes( key ) || null; return pattern || starter; } // Handle category as string or object if (typeof category === "string") { return category === key; } if (category && typeof category === "object") { return category.slug === key; } return false; }); }; // Handle wishlist actions const handleWishlistAction = async (id, action = "", type = "") => { try { const response = await wp.apiFetch({ path: API_ENDPOINTS.WISHLIST, method: "POST", data: { id, action, type }, }); if (!response) { throw new Error(__("No response received from server", "post-carousel")); } if (response.success) { const wishlist = Array.isArray(response.wishListArr) ? response.wishListArr : Object.values(response.wishListArr || {}); setWishlistArr(wishlist); } else { throw new Error(response.message || __("Failed to update wishlist", "post-carousel")); } } catch (error) { console.error("Error updating wishlist:", error); // Optionally show user-friendly error message } }; // Initialize on mount useEffect(() => { handleWishlistAction("", "", "fetchData"); fetchTemplates(); fetchSitesData(); // fetchCategories(); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); }; }, []); return ( {isPopup && ( { console.error("Library Error Boundary:", error, errorInfo); }} onRetry={() => { fetchTemplates(); fetchSitesData() }} >
{!isBlockPattern && } { tabState === "ready-pattern" && ( )}
)}
); }; export default Library;