import React from 'react'; import type { TComponent } from '@namiml/sdk-core'; import { usePaywallContext } from '../context/PaywallContext'; import { conditionComponentMatches, resolveComponentSmartText, withOverrides, } from '../utils/rendering'; interface Props { component: TComponent; scaleFactor?: number; onClose?: () => void; parentDirection?: string; parentCrossAxisFitContent?: boolean; } type RendererProps = { component: TComponent; scaleFactor: number; onClose?: () => void; parentDirection?: string; parentCrossAxisFitContent?: boolean; }; type ComponentRenderer = React.ComponentType; const componentRendererCache = new Map(); function resolveRenderer(rendererKey: string): ComponentRenderer | null { if (componentRendererCache.has(rendererKey)) { return componentRendererCache.get(rendererKey) ?? null; } let renderer: ComponentRenderer | null = null; switch (rendererKey) { case 'NamiButton': renderer = require('./elements/NamiButton').NamiButton; break; case 'NamiText': renderer = require('./elements/NamiText').NamiText; break; case 'NamiSymbol': renderer = require('./elements/NamiSymbol').NamiSymbol; break; case 'NamiImage': renderer = require('./elements/NamiImage').NamiImage; break; case 'NamiSpacer': renderer = require('./elements/NamiSpacer').NamiSpacer; break; case 'NamiVideo': renderer = require('./elements/NamiVideo').NamiVideo; break; case 'NamiContainer': renderer = require('./containers/NamiContainer').NamiContainer; break; case 'NamiProductContainer': renderer = require('./containers/NamiProductContainer').NamiProductContainer; break; case 'NamiStack': renderer = require('./containers/NamiStack').NamiStack; break; case 'NamiCarousel': renderer = require('./containers/NamiCarousel').NamiCarousel; break; case 'NamiCollapseContainer': renderer = require('./containers/NamiCollapseContainer').NamiCollapseContainer; break; case 'NamiResponsiveGrid': renderer = require('./containers/NamiResponsiveGrid').NamiResponsiveGrid; break; case 'NamiRepeatingGrid': renderer = require('./containers/NamiRepeatingGrid').NamiRepeatingGrid; break; case 'NamiSegmentPicker': renderer = require('./elements/NamiSegmentPicker').NamiSegmentPicker; break; case 'NamiSegmentPickerItem': renderer = require('./elements/NamiSegmentPickerItem').NamiSegmentPickerItem; break; case 'NamiToggleSwitch': renderer = require('./elements/NamiToggleSwitch').NamiToggleSwitch; break; case 'NamiTextInput': renderer = require('./elements/NamiTextInput').NamiTextInput; break; case 'NamiRadioButton': renderer = require('./elements/NamiRadioButton').NamiRadioButton; break; case 'NamiProgressIndicator': renderer = require('./elements/NamiProgressIndicator').NamiProgressIndicator; break; case 'NamiProgressBar': renderer = require('./elements/NamiProgressBar').NamiProgressBar; break; case 'NamiCountdownTimer': renderer = require('./elements/NamiCountdownTimer').NamiCountdownTimer; break; case 'NamiToggleButton': renderer = require('./elements/NamiToggleButton').NamiToggleButton; break; case 'NamiPlayPauseButton': renderer = require('./elements/NamiPlayPauseButton').NamiPlayPauseButton; break; case 'NamiVolumeButton': renderer = require('./elements/NamiVolumeButton').NamiVolumeButton; break; case 'NamiQRCode': renderer = require('./elements/NamiQRCode').NamiQRCode; break; default: renderer = null; } componentRendererCache.set(rendererKey, renderer); return renderer; } function renderWithKey( rendererKey: string, props: RendererProps, ): React.ReactElement | null { const Renderer = resolveRenderer(rendererKey); return Renderer ? : null; } export const TemplateRenderer: React.FC = ({ component, scaleFactor = 1, onClose, parentDirection, parentCrossAxisFitContent}) => { const ctx = usePaywallContext(); const smartTextSku = (component as any)?.smartTextSku ?? (component as any)?.sku; // Skip hidden components if (component.hidden) return null; // Handle condition components - evaluate condition, render children if matched if (component.component === 'condition') { const cond = component as any; if (!conditionComponentMatches(ctx, cond, smartTextSku)) return null; // Render children of condition return ( <> {cond.components?.map((child: TComponent, i: number) => ( ))} ); } // Apply conditionAttributes overrides if any match let resolvedComponent = component; if (component.conditionAttributes?.length) { resolvedComponent = withOverrides(ctx, resolvedComponent as any, smartTextSku) as any; } // Resolve smart-text values on component fields (shallow) if (!(resolvedComponent as any).__namiSmartTextResolved) { resolvedComponent = resolveComponentSmartText(resolvedComponent, ctx); } const componentType: string = resolvedComponent.component; return renderResolvedComponent( componentType, resolvedComponent, scaleFactor, onClose, parentDirection, parentCrossAxisFitContent, ); }; function renderResolvedComponent( componentType: string, resolvedComponent: TComponent, scaleFactor: number, onClose?: () => void, parentDirection?: string, parentCrossAxisFitContent?: boolean, ): React.ReactElement | null { const sharedProps: RendererProps = { component: resolvedComponent, scaleFactor, onClose, parentDirection, parentCrossAxisFitContent, }; switch (componentType) { case 'button': return renderWithKey('NamiButton', sharedProps); case 'text': case 'title': case 'body': case 'legal': case 'text-list': return renderWithKey('NamiText', sharedProps); case 'symbol': return renderWithKey('NamiSymbol', sharedProps); case 'image': return renderWithKey('NamiImage', sharedProps); case 'spacer': return renderWithKey('NamiSpacer', sharedProps); case 'videoUrl': return renderWithKey('NamiVideo', sharedProps); case 'container': case 'formContainer': return renderWithKey('NamiContainer', sharedProps); case 'productContainer': return renderWithKey('NamiProductContainer', sharedProps); case 'stack': return renderWithKey('NamiStack', sharedProps); case 'carouselContainer': return renderWithKey('NamiCarousel', sharedProps); case 'collapseContainer': return renderWithKey('NamiCollapseContainer', sharedProps); case 'responsiveGrid': return renderWithKey('NamiResponsiveGrid', sharedProps); case 'repeatingGrid': return renderWithKey('NamiRepeatingGrid', sharedProps); case 'segmentPicker': case 'formSegmentPicker': return renderWithKey('NamiSegmentPicker', sharedProps); case 'segmentPickerItem': return renderWithKey('NamiSegmentPickerItem', sharedProps); case 'toggleSwitch': return renderWithKey('NamiToggleSwitch', sharedProps); case 'textInput': return renderWithKey('NamiTextInput', sharedProps); case 'radio': return renderWithKey('NamiRadioButton', sharedProps); case 'progressIndicator': return renderWithKey('NamiProgressIndicator', sharedProps); case 'progressBar': return renderWithKey('NamiProgressBar', sharedProps); case 'countdownTimerText': return renderWithKey('NamiCountdownTimer', sharedProps); case 'toggleButton': return renderWithKey('NamiToggleButton', sharedProps); case 'playPauseButton': return renderWithKey('NamiPlayPauseButton', sharedProps); case 'volumeButton': return renderWithKey('NamiVolumeButton', sharedProps); case 'qrCode': return renderWithKey('NamiQRCode', sharedProps); default: return null; } }