{{-- Frontend Renderer for Page Builder V2 Pages This file renders pages created with the React + Craft.js builder --}} @php \Log::info('==================== GLOBAL SECTIONS RENDER-V2 START ===================='); // Get the builder data - For Global Sections, use $section->builder_data (not builder_v2_data) $builderData = $section->builder_data ?? []; // Extract components from Craft.js format $rootNode = $builderData['ROOT'] ?? null; // Debug: Log ROOT structure if ($rootNode) { \Log::info('ROOT Node', ['type' => $rootNode['type']['resolvedName'] ?? 'unknown', 'childNodes' => $rootNode['nodes'] ?? []]); } // Get website and its settings $currentWebsite = $section->website ?? null; $currentWebsiteId = $section->website_id ?? null; $currencySymbol = $currentWebsite ? $currentWebsite->getCurrencySymbol() : 'RM'; $websiteSubdomain = $currentWebsite ? $currentWebsite->subdomain : null; /** * Recursively render a Craft.js node and its children */ if (!function_exists('renderNode')) { function renderNode($nodeId, $nodes, $websiteId = null, $currencySymbol = 'RM', $websiteSubdomain = null) { if (!isset($nodes[$nodeId])) { return ''; } $node = $nodes[$nodeId]; $type = $node['type']['resolvedName'] ?? 'div'; \Log::info('renderNode called', ['nodeId' => $nodeId, 'type' => $type]); $props = $node['props'] ?? []; $childNodes = $node['nodes'] ?? []; // Debug log if ($type === 'Posts') { \Log::info('POSTS NODE FOUND!', ['nodeId' => $nodeId, 'type' => $type, 'props' => $props]); } // Render based on component type switch ($type) { case 'Container': return renderContainer($props, $childNodes, $nodes, $websiteId, $currencySymbol, $websiteSubdomain); case 'Heading': return renderHeading($props); case 'Paragraph': return renderParagraph($props); case 'Button': return renderButton($props); case 'Image': return renderImage($props); case 'Row': return renderRow($props, $childNodes, $nodes, $websiteId, $currencySymbol, $websiteSubdomain); case 'Column': return renderColumn($props, $childNodes, $nodes, $websiteId, $currencySymbol, $websiteSubdomain); case 'Card': return renderCard($props); case 'Hero': return renderHero($props); case 'IconBox': return renderIconBox($props); case 'Slider': return renderSlider($props); case 'Accordion': return renderAccordion($props); case 'Tabs': return renderTabs($props); case 'Gallery': return renderGallery($props); case 'Video': return renderVideo($props); case 'Form': return renderForm($props); case 'Testimonials': return renderTestimonials($props); case 'PricingTable': return renderPricingTable($props); case 'TeamMembers': return renderTeamMembers($props); case 'Stats': return renderStats($props); case 'Timeline': return renderTimeline($props); case 'Map': return renderMap($props); case 'CTABanner': return renderCTABanner($props); case 'Divider': return renderDivider($props); case 'Spacer': return renderSpacer($props); case 'Menu': return renderMenu($props, $websiteId); case 'ProductGrid': return renderProductGrid($props, $websiteId, $currencySymbol, $websiteSubdomain); case 'FeaturedProduct': return renderFeaturedProduct($props, $websiteId, $currencySymbol); case 'AddToCart': return renderAddToCart($props, $websiteId, $currencySymbol); case 'BookingForm': return renderBookingForm($props, $websiteId, $currencySymbol); case 'ResourcesList': return renderResourcesList($props, $websiteId, $currencySymbol); case 'PaymentForm': return renderPaymentForm($props); case 'Posts': \Log::info('Posts case matched!', ['props' => $props, 'websiteId' => $websiteId, 'subdomain' => $websiteSubdomain]); $result = renderPosts($props, $websiteId, $websiteSubdomain); \Log::info('renderPosts returned', ['length' => strlen($result), 'preview' => substr($result, 0, 100)]); return $result; default: // Unknown component type - render children in a div $html = '
%s
', $style, nl2br(htmlspecialchars($text)) ); } } /** * Render Button component */ if (!function_exists('renderButton')) { function renderButton($props) { $text = $props['text'] ?? 'Click Me'; $url = $props['url'] ?? '#'; $variant = $props['variant'] ?? 'primary'; $size = $props['size'] ?? 'md'; $spacingStyles = getSpacingStyles($props); $wrapperStyle = !empty($spacingStyles) ? sprintf(' style="%s"', implode('; ', $spacingStyles)) : ''; return sprintf( '', $wrapperStyle, htmlspecialchars($url), $variant, $size, htmlspecialchars($text) ); } } /** * Render Image component */ if (!function_exists('renderImage')) { function renderImage($props) { $src = $props['src'] ?? 'https://via.placeholder.com/800x400'; $alt = $props['alt'] ?? 'Image'; $width = $props['width'] ?? '100%'; $rounded = $props['rounded'] ?? false; $styles = getSpacingStyles($props); $styles[] = sprintf('width: %s', $width); if ($rounded) { $styles[] = 'border-radius: 8px'; } $style = implode('; ', $styles); return sprintf( '%s
', htmlspecialchars($description)); if ($showButton) { $html .= sprintf('%s', htmlspecialchars($buttonUrl), htmlspecialchars($buttonText) ); } $html .= '%s
', $textColor, $textShadow, htmlspecialchars($subtitle)); if ($showButton) { $html .= sprintf('%s', htmlspecialchars($buttonUrl), $buttonVariant, htmlspecialchars($buttonText) ); } $html .= '%s
', $descriptionColor, htmlspecialchars($description)); $html .= '' . htmlspecialchars($image['caption']) . '
' : '' ); } $html .= 'Invalid video URL
' . htmlspecialchars($formDescription) . '
'; } $html .= ''; $html .= '' . htmlspecialchars($plan['description'] ?? '') . '
'; $html .= '' . htmlspecialchars($event['description']) . '
'; $html .= '' . htmlspecialchars($event['description']) . '
'; $html .= 'Add products in your e-commerce dashboard to display them here.
'; $html .= '' . htmlspecialchars($formDescription) . '
'; } $html .= '' . htmlspecialchars($formDescription) . '
'; } $html .= '' . htmlspecialchars($excerpt) . '
'; } // Read More Button if ($showReadMore) { // Generate correct post URL with subdomain $postUrl = $websiteSubdomain ? '/' . $websiteSubdomain . '/post/' . $post->slug : '/posts/' . $post->slug; $html .= ''; $html .= 'Read More '; $html .= ''; } $html .= ''; $html .= htmlspecialchars($product->description); $html .= '
'; } if ($showPrice) { $html .= '' . ($displayMode === 'selected' && empty($selectedResources) ? 'Please select resources to display' : 'No resources found matching your criteria') . '