Preview.tsx 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. import * as React from 'react';
  2. // FIX: Import FormBlock and FormSubmission to handle form rendering and data submission.
  3. import { PageSettings, ThemeName, Block, SocialPlatform, HeaderBlock, DesignSettings, ButtonShape, ButtonStyle, FontFamily, BackgroundType, LinkBlock, VideoBlock, ImageBlock, EmailBlock, PhoneBlock, BannerSettings, MediaSource, TextBlock, AIGCVideo, SocialBlock, ChatMessage, ChatWidgetSettings, PdfBlock, MapBlock, SideNavSettings, NewsBlock, AIGCArticle, ProductBlock, ChatBlock, EnterpriseInfoBlock, EnterpriseInfoIcon, FormBlock, FormSubmission, AwardBlock, FooterBlock } from '../types';
  4. import { Icon } from './ui/Icon';
  5. import ChatWidget from './ChatWidget';
  6. import { sendChatMessage } from '../services/geminiService';
  7. import EnterpriseNav from './EnterpriseNav';
  8. import { format, parseISO } from 'date-fns';
  9. // --- STYLING HELPERS ---
  10. const getImageUrl = (source?: MediaSource): string | undefined => {
  11. if (!source) return undefined;
  12. switch (source.type) {
  13. case 'url': return source.value;
  14. case 'file': return source.value.previewUrl;
  15. default: return undefined;
  16. }
  17. };
  18. var themePresets: Record<ThemeName, { bg: string, text: string, button: string, buttonText: string }> = {
  19. light: { bg: '#F3F4F6', text: '#1F2937', button: '#FFFFFF', buttonText: '#1F2937' },
  20. dark: { bg: '#1F2937', text: '#FFFFFF', button: '#374151', buttonText: '#FFFFFF' },
  21. synthwave: { bg: '#2D3748', text: '#F0D43A', button: '#EC4899', buttonText: '#FFFFFF' },
  22. retro: { bg: '#FEF3C7', text: '#854D0E', button: '#FBBF24', buttonText: '#000000' },
  23. custom: { bg: '', text: '', button: '', buttonText: '' } // Handled separately
  24. };
  25. var socialIconPaths: Record<SocialPlatform, React.ReactElement> = {
  26. twitter: React.createElement("path", { d: "M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.223.085a4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" }),
  27. instagram: React.createElement("path", { d: "M12 2.163c3.204 0 3.584.012 4.85.07 1.17.055 1.805.248 2.227.415.562.217.96.477 1.382.896.413.42.67.824.896 1.383.167.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.013 3.583-.07 4.85c-.054 1.17-.246 1.805-.413 2.227-.228.562-.483.96-.896 1.383-.42.413-.824-.67-1.382-.896-.422-.167-1.057.36-2.227.413-1.266.057-1.646.07-4.85.07s-3.585-.013-4.85-.07c-1.17-.054-1.805-.246-2.227-.413-.562-.228-.96-.483-1.382-.896-.413-.42-.67-.824-.896-1.383-.167-.422-.36-1.057-.413-2.227-.057-1.266-.07-1.646-.07-4.85s.013-3.585.07-4.85c.054-1.17.246 1.805.413-2.227.228.562.483.96.896-1.382.42-.413.824-.67 1.382-.896.422-.167 1.057.36 2.227-.413 1.265-.057 1.646-.07 4.85-.07M12 0C8.74 0 8.333.014 7.053.072 5.775.132 4.905.333 4.14.63c-.784.3-1.2.623-1.77.933-.448.174-.9.34-1.356.59-.446.243-.823.533-1.116.823-.293.29-.58.67-.823 1.116-.25.456-.417.908-.59 1.356-.31.77-.63 1.2-.933 1.77-.298.765-.498 1.635-.558 2.913-.058 1.28-.072 1.687-.072 4.947s.014 3.667.072 4.947c.06 1.278.26 2.148.558 2.913.3.77.623 1.2.933 1.77.174.448.34.9.59 1.356.243.446.533.823.823 1.116.29.293.67.58 1.116.823.456.25.908.417 1.356.59.77.31 1.2.63 1.77.933.765.298 1.635.498 2.913.558 1.28.058 1.687.072 4.947.072s3.667-.014 4.947-.072c1.278-.06 2.148-.26 2.913-.558.77-.3 1.2-.623 1.77-.933.448-.174.9-.34 1.356.59.446-.243.823-.533 1.116.823.293-.29.58-.67.823-1.116.25-.456-.417-.908-.59-1.356.31-.77.63-1.2.933-1.77.298-.765.498 1.635.558-2.913.058-1.28.072-1.687.072-4.947s-.014-3.667-.072-4.947c-.06-1.278-.26-2.148-.558-2.913-.3-.77-.623-1.2-.933-1.77-.174-.448-.34-.9-.59-1.356-.243-.446-.533-.823-.823-1.116-.29-.293-.67-.58-1.116-.823-.456-.25-.908-.417-1.356-.59-.77-.31-1.2-.63-1.77-.933C16.947.333 16.077.132 14.797.072 13.517.014 13.11 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.88 1.44 1.44 0 000-2.88z" }),
  28. facebook: React.createElement("path", { d: "M22.675 0H1.325C.593 0 0 .593 0 1.325v21.351C0 23.407.593 24 1.325 24H12.82v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116c.732 0 1.325-.593 1.325-1.325V1.325C24 .593 23.407 0 22.675 0z" }),
  29. linkedin: React.createElement("path", { d: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" }),
  30. youtube: React.createElement("path", { d: "M21.582 6.186A2.69 2.69 0 0019.54 4.54a49.123 49.123 0 00-7.54-.38A49.123 49.123 0 004.46 4.54a2.69 2.69 0 00-2.042 1.646A28.024 28.024 0 002.31 12a28.024 28.024 0 00.108 5.814A2.69 2.69 0 004.46 19.46a49.123 4.9123 0 007.54.38 49.123 49.123 0 007.54-.38a2.69 2.69 0 002.042-1.646A28.024 28.024 0 0021.69 12a28.024 28.024 0 00-.108-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" }),
  31. tiktok: React.createElement("path", { d: "M12.525.02c1.31-.02 2.61-.01 3.91.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.65 4.31 1.72v3.28c-1.31.08-2.62.06-3.92-.01-1.25-.07-2.47-.62-3.38-1.5-1.2-1.15-1.85-2.76-1.9-4.48v5.72c0 2.55-2.09 4.63-4.66 4.63-2.58 0-4.66-2.09-4.66-4.66s2.08-4.66 4.66-4.66c.33 0 .66.04.98.11v3.24c-1.31-.08-2.62-.06-3.92.01-1.25.07-2.47.62-3.38 1.5-1.2 1.15-1.85 2.76-1.9 4.48v-2.32c0-2.55 2.09-4.63 4.66-4.63 2.58 0 4.66 2.09 4.66 4.66s-2.08 4.66-4.66 4.66c-.33 0-.66-.04-.98-.11C6.25 19.38 5.14 18.25 4.2 17.29v-5.72c0-2.55 2.09-4.63 4.66-4.63.1 0 .2.01.29.02z" }),
  32. github: React.createElement("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" })
  33. };
  34. const enterpriseInfoIcons: Record<EnterpriseInfoIcon, React.ReactElement> = {
  35. building: <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 6.75zM9 12.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 12.75z" />,
  36. bank: <path strokeLinecap="round" strokeLinejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V5.625A2.625 2.625 0 0017.25 3H6.75A2.625 2.625 0 004.5 5.625V21" />,
  37. money: <path strokeLinecap="round" strokeLinejoin="round" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-15c-.621 0-1.125-.504-1.125-1.125v-9.75c0-.621.504-1.125 1.125-1.125h.375m15-1.5v-1.5A.75.75 0 0020.25 4.5h-15a.75.75 0 00-.75.75v1.5m15 0a.75.75 0 01-.75.75H4.5a.75.75 0 01-.75-.75m15 0v.375c0 .621-.504 1.125-1.125 1.125h-15.75c-.621 0-1.125-.504-1.125-1.125v-.375m15.75 0v3.472a60.1 60.1 0 01-15.797-2.102c-.727-.198-1.453.342-1.453 1.096V4.5" />,
  38. location: React.createElement(React.Fragment, {}, React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 10.5a3 3 0 11-6 0 3 3 0 616 0z" }), React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" })),
  39. calendar: <path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0h18M12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zm2.25-2.25h.008v.008H14.25v-.008zm0 2.25h.008v.008H14.25v-.008zm2.25-2.25h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5v-.008zm-4.5-2.25h.008v.008H9.75v-.008zm0 2.25h.008v.008H9.75v-.008z" />,
  40. users: <path strokeLinecap="round" strokeLinejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.53-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-4.663M12 12a4.5 4.5 0 100-9 4.5 4.5 0 000 9z" />,
  41. lightbulb: <path strokeLinecap="round" strokeLinejoin="round" d="M12 18v-5.25m0 0a3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3zm0 0c-1.125 0-2.25.5-2.25 1.5V18m0-5.25c1.125 0 2.25.5 2.25 1.5V18M12 3a9 9 0 100 18 9 9 0 000-18z" />,
  42. };
  43. var getFontClass = function(font: FontFamily) {
  44. switch(font) {
  45. case 'serif': return 'font-serif';
  46. case 'mono': return 'font-mono';
  47. case 'sans': default: return 'font-sans';
  48. }
  49. };
  50. const getFontFamilyValue = (font: FontFamily) => {
  51. switch (font) {
  52. case 'serif': return 'serif';
  53. case 'mono': return 'monospace';
  54. case 'sans': default: return 'sans-serif';
  55. }
  56. }
  57. var getButtonClasses = function(shape: ButtonShape, style: ButtonStyle) {
  58. var classes = 'w-full p-3 font-semibold transition-transform transform hover:scale-105 shadow-md flex items-center justify-center gap-4 text-center ';
  59. if(shape === 'pill') classes += 'rounded-full';
  60. else if(shape === 'square') classes += 'rounded-none';
  61. else classes += 'rounded-lg';
  62. if(style === 'outline') classes += ' bg-transparent border-2';
  63. return classes;
  64. };
  65. var getBackgroundStyle = function(type: BackgroundType, value: string) {
  66. if (type === 'image') return { backgroundImage: "url(" + value + ")", backgroundSize: 'cover', backgroundPosition: 'center' };
  67. if (type === 'color') return { backgroundColor: value };
  68. return {}; // for gradient, we use a class
  69. };
  70. var getBackgroundClass = function(type: BackgroundType, value: string) {
  71. return type === 'gradient' ? value : '';
  72. };
  73. var getBannerStyle = function(settings: BannerSettings): React.CSSProperties {
  74. var style: React.CSSProperties = { height: settings.height + "px" };
  75. const imageUrl = getImageUrl(settings.imageSource);
  76. if (settings.type === 'image' && imageUrl) {
  77. style.backgroundImage = "url(" + imageUrl + ")";
  78. style.backgroundSize = 'cover';
  79. style.backgroundPosition = 'center';
  80. }
  81. if (settings.type === 'color') {
  82. style = Object.assign({}, style, { backgroundColor: settings.value });
  83. }
  84. return style;
  85. };
  86. var getBannerClass = function(settings: BannerSettings) {
  87. var classes = '';
  88. if (settings.type === 'gradient') {
  89. classes += " " + settings.value;
  90. }
  91. return classes;
  92. };
  93. // --- Child Components ---
  94. var ActionModal: React.FC<{
  95. data: { type: 'email' | 'phone'; value: string; label: string; } | null;
  96. onClose: () => void;
  97. }> = function(props) {
  98. var data = props.data, onClose = props.onClose;
  99. if (!data) return null;
  100. var isEmail = data.type === 'email';
  101. var actionText = isEmail ? 'Open Mail App' : 'Call';
  102. var actionHref = isEmail ? "mailto:" + data.value : "tel:" + data.value;
  103. var handleCopy = function() {
  104. navigator.clipboard.writeText(data.value).then(function() {
  105. alert((isEmail ? 'Email' : 'Number') + " copied to clipboard!");
  106. onClose();
  107. });
  108. };
  109. return (
  110. React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center z-[100]", onClick: () => onClose() } as any,
  111. React.createElement("div", { className: "bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-xs p-6 text-center", onClick: function(e) { e.stopPropagation(); } } as any,
  112. React.createElement("h3", { className: "font-bold text-lg mb-2 text-gray-900 dark:text-white" }, data.label),
  113. React.createElement("p", { className: "text-gray-600 dark:text-gray-300 mb-6 break-words" }, data.value),
  114. React.createElement("div", { className: "space-y-3" },
  115. React.createElement("button", { onClick: handleCopy, className: "w-full bg-brand-primary text-white font-semibold py-3 rounded-lg" }, "Copy " + (isEmail ? 'Email' : 'Number')),
  116. React.createElement("a", { href: actionHref, className: "block w-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold py-3 rounded-lg" }, actionText),
  117. React.createElement("button", { onClick: () => onClose(), className: "mt-4 w-full text-sm text-gray-500 dark:text-gray-400" } as any, "Cancel")
  118. )
  119. )
  120. )
  121. );
  122. };
  123. var PersonalChatOverlay: React.FC<{ isOpen: boolean; onClose: () => void; settings: ChatWidgetSettings }> = function(props) {
  124. var isOpen = props.isOpen, onClose = props.onClose, settings = props.settings;
  125. var messagesState = React.useState<ChatMessage[]>([]);
  126. var messages = messagesState[0];
  127. var setMessages = messagesState[1];
  128. var userInputState = React.useState('');
  129. var userInput = userInputState[0];
  130. var setUserInput = userInputState[1];
  131. var isLoadingState = React.useState(false);
  132. var isLoading = isLoadingState[0];
  133. var setIsLoading = isLoadingState[1];
  134. var isRecordingState = React.useState(false);
  135. var isRecording = isRecordingState[0];
  136. var setIsRecording = isRecordingState[1];
  137. var messagesEndRef = React.useRef<HTMLDivElement>(null);
  138. var recognitionRef = React.useRef<any>(null);
  139. React.useEffect(function() {
  140. // @ts-ignore
  141. var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  142. if (SpeechRecognition) {
  143. recognitionRef.current = new SpeechRecognition();
  144. recognitionRef.current.continuous = false;
  145. recognitionRef.current.lang = 'en-US';
  146. recognitionRef.current.interimResults = false;
  147. recognitionRef.current.maxAlternatives = 1;
  148. recognitionRef.current.onresult = function(event: any) {
  149. var transcript = event.results[0][0].transcript;
  150. setUserInput(transcript);
  151. setIsRecording(false);
  152. };
  153. recognitionRef.current.onerror = function(event: any) {
  154. console.error('Speech recognition error:', event.error);
  155. setIsRecording(false);
  156. };
  157. recognitionRef.current.onend = function() {
  158. setIsRecording(false);
  159. };
  160. }
  161. }, []);
  162. React.useEffect(function() {
  163. if (isOpen && messages.length === 0) {
  164. var initialMessage: ChatMessage = { id: "msg-ai-" + Date.now(), sender: 'ai', text: 'Hello! How can I help you today?' };
  165. setMessages([initialMessage]);
  166. }
  167. }, [isOpen, messages.length]);
  168. React.useEffect(function() {
  169. if (messagesEndRef.current) {
  170. messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
  171. }
  172. }, [messages, isLoading]);
  173. var handleSendMessage = React.useCallback(function() {
  174. if (!userInput.trim()) return;
  175. var userMessage: ChatMessage = { id: "msg-user-" + Date.now(), sender: 'user', text: userInput };
  176. setMessages(function(prev) { return prev.concat([userMessage]); });
  177. var messageToSend = userInput;
  178. setUserInput('');
  179. setIsLoading(true);
  180. sendChatMessage(messageToSend).then(function(aiResponse) {
  181. setMessages(function(prev) { return prev.concat([aiResponse]); });
  182. setIsLoading(false);
  183. });
  184. }, [userInput]);
  185. var toggleRecording = function() {
  186. if (!recognitionRef.current) {
  187. alert("Speech recognition is not supported by your browser.");
  188. return;
  189. }
  190. if (isRecording) {
  191. recognitionRef.current.stop();
  192. } else {
  193. recognitionRef.current.start();
  194. }
  195. setIsRecording(!isRecording);
  196. };
  197. return React.createElement("div", {
  198. className: "absolute inset-0 bg-black/30 z-50 transition-opacity duration-300 " + (isOpen ? "opacity-100" : "opacity-0 pointer-events-none"),
  199. onClick: function() { return onClose(); }
  200. } as any,
  201. React.createElement("div", {
  202. className: "absolute bottom-0 left-0 right-0 h-3/4 bg-white dark:bg-gray-800 rounded-t-2xl shadow-2xl flex flex-col transition-transform duration-300 " + (isOpen ? "translate-y-0" : "translate-y-full"),
  203. style: { backgroundColor: settings.panelBackgroundColor },
  204. onClick: function(e) { e.stopPropagation(); }
  205. } as any,
  206. React.createElement("div", { className: "flex-shrink-0 p-4 flex justify-between items-center rounded-t-2xl", style: { backgroundColor: settings.headerBackgroundColor, color: settings.headerTextColor } } as any,
  207. React.createElement("h3", { className: "font-bold text-lg" }, "AI Assistant"),
  208. React.createElement("button", { onClick: () => onClose(), style: { color: settings.headerTextColor } } as any,
  209. React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
  210. )
  211. ),
  212. React.createElement("div", { className: "flex-1 p-4 overflow-y-auto space-y-4" } as any,
  213. messages.map(function(msg) {
  214. return React.createElement("div", { key: msg.id, className: "flex items-start gap-3 " + (msg.sender === 'user' ? 'justify-end' : '') },
  215. msg.sender === 'ai' && React.createElement("div", { className: "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0", style: { backgroundColor: settings.aiMessageBackgroundColor, color: settings.aiMessageTextColor } }, "🤖"),
  216. React.createElement("div", { className: "max-w-xs p-3 rounded-lg text-sm", style: { backgroundColor: msg.sender === 'user' ? settings.userMessageBackgroundColor : settings.aiMessageBackgroundColor, color: msg.sender === 'user' ? settings.userMessageTextColor : settings.aiMessageTextColor, borderRadius: msg.sender === 'user' ? '1rem 1rem 0.25rem 1rem' : '1rem 1rem 1rem 0.25rem' } } as any,
  217. React.createElement("p", null, msg.text)
  218. )
  219. );
  220. }),
  221. isLoading && React.createElement("div", { className: "flex items-start gap-3" },
  222. React.createElement("div", { className: "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0", style: { backgroundColor: settings.aiMessageBackgroundColor } }, "🤖"),
  223. React.createElement("div", { className: "max-w-xs p-3 rounded-lg", style: { backgroundColor: settings.aiMessageBackgroundColor } } as any,
  224. React.createElement("div", { className: "flex items-center space-x-1" },
  225. React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-0", style: { backgroundColor: settings.aiMessageTextColor } }),
  226. React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-150", style: { backgroundColor: settings.aiMessageTextColor } }),
  227. React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-300", style: { backgroundColor: settings.aiMessageTextColor } })
  228. )
  229. )
  230. ),
  231. React.createElement("div", { ref: messagesEndRef })
  232. ),
  233. React.createElement("div", { className: "flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700 flex items-center gap-3" } as any,
  234. React.createElement("button", { onClick: toggleRecording, className: "p-2 rounded-full " + (isRecording ? "bg-red-500 animate-pulse" : "bg-gray-200 dark:bg-gray-700") } as any,
  235. React.createElement(Icon, { className: "h-6 w-6 text-gray-800 dark:text-white", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" }) })
  236. ),
  237. React.createElement("input", { type: "text", value: userInput, onChange: function(e) { return setUserInput(e.target.value); }, onKeyPress: function(e) { return e.key === 'Enter' && !isLoading && handleSendMessage(); }, placeholder: "Ask me anything...", className: "flex-1 bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-brand-primary focus:outline-none", disabled: isLoading } as any),
  238. React.createElement("button", { onClick: handleSendMessage, disabled: isLoading, className: "p-2 rounded-full text-white disabled:opacity-50", style: { backgroundColor: settings.headerBackgroundColor } } as any,
  239. React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 10l7-7m0 0l7 7m-7-7v18" }) })
  240. )
  241. )
  242. )
  243. );
  244. };
  245. // --- MAIN PREVIEW COMPONENT ---
  246. interface PreviewProps {
  247. pageSettings: PageSettings;
  248. aigcVideos: AIGCVideo[];
  249. aigcArticles: AIGCArticle[];
  250. previewMode: 'personal' | 'enterprise';
  251. deviceView: 'pc' | 'mobile';
  252. onFormSubmit: (submission: FormSubmission) => void;
  253. }
  254. const FormBlockComponent: React.FC<{
  255. block: FormBlock;
  256. theme: { text: string; button: string; buttonText: string };
  257. onFormSubmit: (submission: FormSubmission) => void;
  258. }> = ({ block, theme, onFormSubmit }) => {
  259. const [formData, setFormData] = React.useState<Record<string, string>>({});
  260. const inputClasses = "w-full p-2 rounded-md bg-white/10 border border-white/20 focus:outline-none focus:ring-1 focus:ring-white";
  261. const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
  262. setFormData({
  263. ...formData,
  264. [e.target.name]: e.target.value,
  265. });
  266. };
  267. const handleSubmit = (e: React.FormEvent) => {
  268. e.preventDefault();
  269. const submission: FormSubmission = {
  270. id: `sub_${Date.now()}`,
  271. formId: block.id,
  272. visitorId: 'visitor_123', // mock visitor id
  273. timestamp: new Date().toISOString(),
  274. data: formData,
  275. };
  276. onFormSubmit(submission);
  277. // Reset form
  278. const emptyData: Record<string, string> = {};
  279. block.fields.filter(f => f.enabled).forEach(f => emptyData[f.id] = '');
  280. if (block.purposeOptions.length > 0) {
  281. emptyData['purpose'] = block.purposeOptions[0].label;
  282. }
  283. setFormData(emptyData);
  284. };
  285. React.useEffect(() => {
  286. const initialData: Record<string, string> = {};
  287. if (block.purposeOptions.length > 0) {
  288. initialData['purpose'] = block.purposeOptions[0].label;
  289. }
  290. setFormData(initialData);
  291. }, [block.purposeOptions]);
  292. return (
  293. <div style={{ backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text }} className="p-6 rounded-lg">
  294. <h3 className="text-xl font-bold mb-2 text-center" style={{ color: theme.text }}>{block.title}</h3>
  295. <p className="text-sm opacity-80 mb-6 text-center" style={{ color: theme.text }}>{block.description}</p>
  296. <form onSubmit={handleSubmit} className="space-y-4">
  297. {block.fields.filter(f => f.enabled).map(field => (
  298. <div key={field.id} className="flex items-center gap-4">
  299. <label htmlFor={field.id} className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
  300. {field.label} {field.required && <span className="text-red-500">*</span>}
  301. </label>
  302. <input
  303. type={field.id === 'email' ? 'email' : field.id === 'phone' ? 'tel' : 'text'}
  304. id={field.id}
  305. name={field.id}
  306. required={field.required}
  307. onChange={handleInputChange}
  308. value={formData[field.id] || ''}
  309. className={`${inputClasses} flex-1`}
  310. style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
  311. />
  312. </div>
  313. ))}
  314. {block.purposeOptions.length > 0 && (
  315. <div className="flex items-center gap-4">
  316. <label htmlFor="purpose" className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
  317. Purpose
  318. </label>
  319. <select
  320. id="purpose"
  321. name="purpose"
  322. onChange={handleInputChange}
  323. value={formData['purpose'] || block.purposeOptions[0].label}
  324. className={`${inputClasses} flex-1`}
  325. style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
  326. >
  327. {block.purposeOptions.map(opt => <option key={opt.id} value={opt.label} style={{backgroundColor: '#374151'}}>{opt.label}</option>)}
  328. </select>
  329. </div>
  330. )}
  331. <button type="submit" style={{ backgroundColor: theme.button, color: theme.buttonText }} className="w-full p-3 font-semibold rounded-lg">
  332. {block.submitButtonText}
  333. </button>
  334. </form>
  335. </div>
  336. );
  337. };
  338. export var Preview: React.FC<PreviewProps> = function(props) {
  339. var { pageSettings, aigcVideos, aigcArticles, previewMode, deviceView, onFormSubmit } = props;
  340. var design = pageSettings.design;
  341. var blocks = pageSettings.blocks;
  342. var theme = design.theme === 'custom'
  343. ? {
  344. bg: design.customThemeColors.background,
  345. text: design.customThemeColors.text,
  346. button: design.customThemeColors.button,
  347. buttonText: design.customThemeColors.buttonText
  348. }
  349. : themePresets[design.theme];
  350. if (design.fontColor) {
  351. theme.text = design.fontColor;
  352. }
  353. var actionModalDataState = React.useState<{ type: 'email' | 'phone'; value: string; label: string; } | null>(null);
  354. var actionModalData = actionModalDataState[0];
  355. var setActionModalData = actionModalDataState[1];
  356. var isChatOpenState = React.useState(false);
  357. var isChatOpen = isChatOpenState[0];
  358. var setIsChatOpen = isChatOpenState[1];
  359. var scrollContainerRef = React.useRef<HTMLDivElement>(null);
  360. const chatBlock = blocks.find(b => b.type === 'chat' && b.visible) as ChatBlock | undefined;
  361. const handleViewGeneratedArticle = (article: AIGCArticle) => {
  362. const isDark = design.theme === 'dark' || design.theme === 'synthwave';
  363. const styles = `
  364. body {
  365. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  366. line-height: 1.6;
  367. color: ${isDark ? '#e5e7eb' : '#1f2937'};
  368. background-color: ${isDark ? '#111827' : '#ffffff'};
  369. max-width: 800px;
  370. margin: 40px auto;
  371. padding: 20px;
  372. }
  373. .prose { max-width: 65ch; margin: 0 auto; }
  374. .prose h1, .prose h2, .prose h3 { color: inherit; }
  375. .prose a { color: #10b981; }
  376. .prose img { max-width: 100%; height: auto; border-radius: 8px; }
  377. .prose .lead { font-size: 1.25em; color: ${isDark ? '#9ca3af' : '#6b7280'}; }
  378. `;
  379. const articleHtml = `
  380. <!DOCTYPE html>
  381. <html lang="en">
  382. <head>
  383. <meta charset="UTF-8">
  384. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  385. <title>${article.title}</title>
  386. <style>${styles}</style>
  387. </head>
  388. <body>
  389. <article class="prose">
  390. <h1>${article.title}</h1>
  391. <p class="lead">${article.summary}</p>
  392. <hr>
  393. <div>${article.content}</div>
  394. </article>
  395. </body>
  396. </html>
  397. `;
  398. const newWindow = window.open();
  399. if (newWindow) {
  400. newWindow.document.write(articleHtml);
  401. newWindow.document.close();
  402. }
  403. };
  404. React.useEffect(function() {
  405. if (scrollContainerRef.current) {
  406. scrollContainerRef.current.scrollTop = 0;
  407. }
  408. }, [previewMode, deviceView]);
  409. React.useEffect(function() {
  410. if (isChatOpen) {
  411. document.body.style.overflow = 'hidden';
  412. } else {
  413. document.body.style.overflow = 'unset';
  414. }
  415. return function() {
  416. document.body.style.overflow = 'unset';
  417. };
  418. }, [isChatOpen]);
  419. var visibleBlocks = blocks.filter(function(b) { return b.visible; });
  420. var renderBlock = function(block: Block) {
  421. var isMobile = deviceView === 'mobile';
  422. const lineClampStyle = (lines: number): React.CSSProperties => ({
  423. display: '-webkit-box',
  424. WebkitLineClamp: lines,
  425. WebkitBoxOrient: 'vertical',
  426. overflow: 'hidden',
  427. textOverflow: 'ellipsis',
  428. });
  429. switch (block.type) {
  430. case 'header':
  431. var headerBlock = block as HeaderBlock;
  432. return React.createElement("h2", { className: "text-2xl font-bold pt-4", style: { color: theme.text, textAlign: headerBlock.titleAlignment } }, headerBlock.text);
  433. case 'link':
  434. var linkBlock = block as LinkBlock;
  435. return (
  436. React.createElement("a", { href: linkBlock.url, target: "_blank", rel: "noopener noreferrer", className: getButtonClasses(design.buttonShape, design.buttonStyle), style: { backgroundColor: design.buttonStyle === 'filled' ? theme.button : 'transparent', color: theme.buttonText, borderColor: theme.buttonText } },
  437. linkBlock.iconUrl && React.createElement("img", { src: linkBlock.iconUrl, alt: "", className: "w-6 h-6 rounded-md" }),
  438. React.createElement("span", { className: "flex-1" }, linkBlock.title)
  439. )
  440. );
  441. case 'chat': {
  442. const currentChatBlock = block as ChatBlock;
  443. if (previewMode === 'personal' && currentChatBlock.layout !== 'button') return null;
  444. if (currentChatBlock.layout === 'widget') return null;
  445. const chatButtonStyle: React.CSSProperties = {};
  446. if (design.buttonStyle === 'filled') {
  447. chatButtonStyle.backgroundColor = '#10b981';
  448. chatButtonStyle.color = '#ffffff';
  449. } else {
  450. chatButtonStyle.backgroundColor = 'transparent';
  451. chatButtonStyle.color = '#10b981';
  452. chatButtonStyle.borderColor = '#10b981';
  453. }
  454. return React.createElement("button", {
  455. onClick: () => setIsChatOpen(true),
  456. className: getButtonClasses(design.buttonShape, design.buttonStyle) + ' h-16',
  457. style: chatButtonStyle
  458. } as any, "Chat with me");
  459. }
  460. case 'social':
  461. var socialBlock = block as SocialBlock;
  462. return (
  463. React.createElement("div", { className: "flex justify-center gap-4" },
  464. socialBlock.links.map(function(link) {
  465. var icon = socialIconPaths[link.platform];
  466. return React.createElement("a", { key: link.id, href: link.url, target: "_blank", rel: "noopener noreferrer", style: { color: theme.text }, className: "hover:opacity-75" },
  467. React.createElement("svg", {
  468. className: "h-8 w-8",
  469. fill: "currentColor",
  470. viewBox: "0 0 24 24"
  471. }, icon)
  472. );
  473. })
  474. )
  475. );
  476. case 'video':
  477. var videoBlock = block as VideoBlock;
  478. var videoLayoutClass = videoBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
  479. return (
  480. React.createElement("div", { className: videoLayoutClass },
  481. videoBlock.sources.map(function(source, index) {
  482. var video = source.type === 'aigc' ? aigcVideos.filter(function(v) { return v.id === source.videoId; })[0] : null;
  483. var url = source.type === 'url' ? source.value : (video && video.videoUrl);
  484. if (!url) return null;
  485. return React.createElement("video", { key: index, src: url, controls: true, className: "w-full rounded-lg aspect-video" });
  486. })
  487. )
  488. );
  489. case 'image':
  490. var imageBlock = block as ImageBlock;
  491. var imageLayoutClass = imageBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
  492. return (
  493. React.createElement("div", { className: imageLayoutClass },
  494. imageBlock.sources.map(function(source, index) {
  495. var url = source.type === 'url' ? source.value : (source.type === 'file' ? source.value.previewUrl : '');
  496. return React.createElement("img", { key: index, src: url, alt: "", className: "w-full rounded-lg object-cover aspect-video" });
  497. })
  498. )
  499. );
  500. case 'text':
  501. var textBlock = block as TextBlock;
  502. return (
  503. React.createElement("p", { style: {
  504. color: textBlock.fontColor || theme.text,
  505. fontSize: textBlock.fontSize,
  506. textAlign: textBlock.textAlign,
  507. fontWeight: textBlock.isBold ? 'bold' : 'normal',
  508. fontStyle: textBlock.isItalic ? 'italic' : 'normal'
  509. } },
  510. textBlock.content
  511. )
  512. );
  513. case 'enterprise_info': {
  514. const infoBlock = block as EnterpriseInfoBlock;
  515. if (!infoBlock.items || infoBlock.items.length === 0) return null;
  516. const isCentered = infoBlock.alignment === 'center';
  517. const containerClasses = isCentered ? 'flex flex-col items-center text-center gap-6' : 'space-y-4';
  518. const itemClasses = isCentered ? 'flex flex-col items-center gap-2' : 'flex items-start gap-4';
  519. return (
  520. React.createElement("div", { className: `p-4 rounded-lg`, style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
  521. React.createElement("div", { className: containerClasses },
  522. infoBlock.items.map(item => (
  523. React.createElement("div", { key: item.id, className: itemClasses },
  524. React.createElement("div", { className: `flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-lg`, style: { backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text } },
  525. React.createElement(Icon, { className: "h-6 w-6", children: enterpriseInfoIcons[item.icon] })
  526. ),
  527. React.createElement("div", { className: "min-w-0" },
  528. React.createElement("p", { className: "text-sm font-semibold uppercase tracking-wider", style: { color: theme.text, opacity: 0.7 } }, item.label),
  529. React.createElement("p", { className: "font-semibold text-base break-words", style: { color: theme.text } }, item.value)
  530. )
  531. )
  532. ))
  533. )
  534. )
  535. );
  536. }
  537. case 'news': {
  538. const newsBlock = block as NewsBlock;
  539. const isGrid = newsBlock.layout === 'grid';
  540. const containerClass = isGrid ? 'grid grid-cols-1 md:grid-cols-2 gap-4' : 'space-y-4';
  541. const extractFirstImageUrl = (htmlContent: string): string | null => {
  542. if (typeof DOMParser === 'undefined') return null;
  543. try {
  544. const doc = new DOMParser().parseFromString(htmlContent, 'text/html');
  545. const img = doc.querySelector('img');
  546. return img ? img.src : null;
  547. } catch (e) {
  548. console.error("Error parsing HTML for image extraction", e);
  549. return null;
  550. }
  551. };
  552. if (newsBlock.source === 'custom') {
  553. const customItems = newsBlock.customItems || [];
  554. if (customItems.length === 0) return null;
  555. return React.createElement("div", { className: containerClass },
  556. customItems.map(item => (
  557. React.createElement("a", {
  558. key: item.id,
  559. href: item.url,
  560. target: "_blank",
  561. rel: "noopener noreferrer",
  562. className: `block w-full text-left p-4 rounded-lg transition-shadow hover:shadow-lg bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700`
  563. },
  564. React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
  565. React.createElement("p", { className: "text-sm text-brand-primary truncate mt-1" }, item.url),
  566. React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, item.summary)
  567. )
  568. ))
  569. );
  570. }
  571. const articlesToShow = (newsBlock.articleIds || [])
  572. .map(id => aigcArticles.find(a => a.id === id))
  573. .filter((a): a is AIGCArticle => !!a);
  574. if (articlesToShow.length === 0) return null;
  575. return React.createElement("div", { className: containerClass },
  576. articlesToShow.map(article => {
  577. const imageUrl = extractFirstImageUrl(article.content);
  578. const hasUrl = article.sourceType === 'url' && article.sourceUrl;
  579. const linkProps: any = {
  580. key: article.id,
  581. className: `block w-full text-left rounded-lg transition-shadow hover:shadow-lg bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden`,
  582. };
  583. if (hasUrl) {
  584. linkProps.href = article.sourceUrl;
  585. linkProps.target = "_blank";
  586. linkProps.rel = "noopener noreferrer";
  587. } else {
  588. linkProps.href = "#";
  589. linkProps.onClick = (e: React.MouseEvent) => {
  590. e.preventDefault();
  591. handleViewGeneratedArticle(article);
  592. };
  593. }
  594. const articleContent = isGrid ? (
  595. React.createElement(React.Fragment, null,
  596. imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-full h-32 object-cover" }),
  597. React.createElement("div", { className: "p-4" },
  598. React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, article.title),
  599. React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
  600. React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, article.summary)
  601. )
  602. )
  603. ) : ( // List View
  604. React.createElement("div", { className: "flex items-start gap-4 p-4" },
  605. imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-40 h-24 object-cover rounded-md flex-shrink-0" }),
  606. React.createElement("div", { className: "flex-1 min-w-0" },
  607. React.createElement("h3", { className: "font-bold text-lg", style: { ...{ color: theme.text }, ...lineClampStyle(2) } }, article.title),
  608. React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
  609. React.createElement("p", { className: "text-sm mt-2", style: { ...{ color: theme.text, opacity: 0.9 }, ...lineClampStyle(2) } }, article.summary)
  610. )
  611. )
  612. );
  613. return React.createElement("a", linkProps, articleContent);
  614. })
  615. );
  616. }
  617. case 'email':
  618. case 'phone':
  619. var actionBlock = block as EmailBlock | PhoneBlock;
  620. var iconPath = actionBlock.type === 'email' ?
  621. React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) :
  622. React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" });
  623. var buttonContent = React.createElement("div", {
  624. className: "flex flex-col items-center justify-center text-center"
  625. },
  626. React.createElement("div", { className: "flex items-center gap-2" },
  627. React.createElement(Icon, { className: "h-5 w-5 flex-shrink-0", children: iconPath }),
  628. React.createElement("span", null, actionBlock.label)
  629. ),
  630. actionBlock.displayMode === 'labelAndValue' && React.createElement("span", {
  631. className: "text-xs opacity-80 mt-1 font-normal"
  632. }, actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone)
  633. );
  634. return React.createElement("button", {
  635. onClick: function() { return setActionModalData({ type: actionBlock.type, value: actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone, label: actionBlock.label }); },
  636. className: getButtonClasses(design.buttonShape, design.buttonStyle),
  637. style: {
  638. backgroundColor: design.buttonStyle === 'filled' ? theme.button : 'transparent',
  639. color: theme.buttonText,
  640. borderColor: theme.buttonText
  641. }
  642. } as any, buttonContent);
  643. case 'map':
  644. var mapBlock = block as MapBlock;
  645. var encodedAddress = encodeURIComponent(mapBlock.address);
  646. var displayStyle = mapBlock.displayStyle || 'interactiveMap';
  647. if (displayStyle === 'imageOverlay') {
  648. var imageUrl = getImageUrl(mapBlock.backgroundImageSource);
  649. return React.createElement("a", {
  650. href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
  651. target: "_blank",
  652. rel: "noopener noreferrer",
  653. className: "block rounded-lg overflow-hidden relative h-48 group"
  654. },
  655. React.createElement("div", {
  656. className: "absolute inset-0 bg-cover bg-center transition-transform duration-300 group-hover:scale-105",
  657. style: { backgroundImage: "url(" + (imageUrl || ("https://picsum.photos/seed/mapbg-" + mapBlock.id + "/600/400")) + ")" }
  658. }),
  659. React.createElement("div", {
  660. className: "absolute inset-0 bg-black/60 flex items-end p-4"
  661. },
  662. React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
  663. )
  664. );
  665. }
  666. return React.createElement("a", {
  667. href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
  668. target: "_blank",
  669. rel: "noopener noreferrer",
  670. className: "block rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:border-brand-primary transition-colors"
  671. },
  672. React.createElement("div", {
  673. className: "relative h-48 bg-cover bg-center",
  674. style: { backgroundImage: "url(https://picsum.photos/seed/map" + mapBlock.id + "/600/400)" }
  675. },
  676. React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-end p-4" },
  677. React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
  678. )
  679. )
  680. );
  681. case 'pdf':
  682. var pdfBlock = block as PdfBlock;
  683. var source = pdfBlock.source;
  684. var url: string | undefined;
  685. var name: string | undefined;
  686. var previewUrl: string | undefined;
  687. if (source.type === 'file') {
  688. url = '#';
  689. name = source.value.name;
  690. previewUrl = source.value.previewUrl;
  691. } else if (source.type === 'url') {
  692. url = source.value;
  693. try {
  694. var urlParts = new URL(url).pathname.split('/');
  695. name = urlParts[urlParts.length - 1] || "Document.pdf";
  696. } catch (e) {
  697. name = "Document.pdf";
  698. }
  699. previewUrl = 'https://api.iconify.design/mdi:file-pdf-box.svg?color=%23' + (theme.text.replace('#',''));
  700. } else {
  701. return null;
  702. }
  703. var isFile = source.type === 'file';
  704. var content = React.createElement("div", { className: "flex items-center gap-4" },
  705. React.createElement("img", { src: previewUrl, className: "w-16 h-20 object-contain rounded-md flex-shrink-0 bg-gray-200 dark:bg-gray-600 p-2" }),
  706. React.createElement("div", { className: "flex-1 min-w-0" },
  707. React.createElement("p", { className: "font-semibold truncate", style: { color: theme.text } }, name),
  708. !isFile && url && React.createElement("p", { className: "text-sm text-brand-primary" }, "View PDF")
  709. )
  710. );
  711. if (!isFile && url) {
  712. return React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "block bg-gray-100 dark:bg-gray-700/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-brand-primary" }, content);
  713. } else {
  714. return React.createElement("div", { className: "bg-gray-100 dark:bg-gray-700/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700" }, content);
  715. }
  716. case 'product': {
  717. const productBlock = block as ProductBlock;
  718. if (productBlock.items.length === 0) return null;
  719. const isGrid = productBlock.layout === 'grid';
  720. const containerClass = isGrid ? 'grid grid-cols-2 gap-4' : 'space-y-4';
  721. const cardClass = `block w-full text-left rounded-lg transition-shadow hover:shadow-lg overflow-hidden border`;
  722. const cardBgStyle = {
  723. backgroundColor: theme.button,
  724. borderColor: design.buttonStyle === 'outline' ? theme.buttonText : 'transparent',
  725. };
  726. return (
  727. React.createElement("div", { className: containerClass },
  728. productBlock.items.map(item => (
  729. React.createElement("a", {
  730. key: item.id,
  731. href: item.url,
  732. target: "_blank",
  733. rel: "noopener noreferrer",
  734. className: cardClass,
  735. style: cardBgStyle
  736. },
  737. React.createElement("img", { src: item.imageUrl, alt: item.title, className: `w-full object-cover ${isGrid ? 'aspect-square' : 'aspect-video'}` }),
  738. React.createElement("div", { className: "p-4" },
  739. React.createElement("h3", { className: "font-bold", style: { color: theme.buttonText } }, item.title),
  740. React.createElement("p", { className: "font-semibold mt-1", style: { color: theme.buttonText, opacity: 0.9 } }, item.price)
  741. )
  742. )
  743. ))
  744. )
  745. );
  746. }
  747. case 'award': {
  748. const awardBlock = block as AwardBlock;
  749. if (!awardBlock.items || awardBlock.items.length === 0) return null;
  750. const isGrid = awardBlock.layout === 'grid';
  751. const containerClass = isGrid ? `grid ${isMobile ? 'grid-cols-1' : 'grid-cols-2'} gap-4` : 'space-y-4';
  752. return (
  753. React.createElement("div", { className: containerClass },
  754. awardBlock.items.map(item => {
  755. const imageUrl = getImageUrl(item.imageSource);
  756. if (isGrid) {
  757. return (
  758. React.createElement("div", { key: item.id, className: "flex flex-col items-center text-center gap-3 p-6 rounded-lg shadow-sm", style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
  759. imageUrl && (
  760. React.createElement("div", { className: "p-1 rounded-full bg-gradient-to-br from-yellow-300 via-amber-400 to-orange-500 flex-shrink-0" },
  761. React.createElement("img", { src: imageUrl, alt: item.title, className: "w-24 h-24 rounded-full object-cover border-4 border-transparent", style: { borderColor: theme.bg } })
  762. )
  763. ),
  764. React.createElement("div", { className: "flex-1 min-w-0" },
  765. item.year && (
  766. React.createElement("span", { className: "text-sm font-semibold opacity-70", style: { color: theme.text } },
  767. item.year
  768. )
  769. ),
  770. React.createElement("h4", { className: "font-bold text-xl truncate w-full", style: { color: theme.text }, title: item.title }, item.title),
  771. item.subtitle && (
  772. React.createElement("p", { className: "text-base opacity-80 break-words", style: { color: theme.text } }, item.subtitle)
  773. )
  774. )
  775. )
  776. );
  777. } else {
  778. return (
  779. React.createElement("div", { key: item.id, className: "flex items-center gap-4 p-4 rounded-lg shadow-sm", style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
  780. imageUrl && (
  781. React.createElement("div", { className: "p-0.5 rounded-full bg-gradient-to-br from-yellow-300 via-amber-400 to-orange-500 flex-shrink-0" },
  782. React.createElement("img", { src: imageUrl, alt: item.title, className: "w-16 h-16 rounded-full object-cover border-2 border-transparent", style: { borderColor: theme.bg } })
  783. )
  784. ),
  785. React.createElement("div", { className: "flex-1 min-w-0" },
  786. React.createElement("div", { className: "flex justify-between items-start" },
  787. React.createElement("h4", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
  788. item.year && (
  789. React.createElement("span", { className: "text-xs font-semibold px-2 py-0.5 rounded-full flex-shrink-0 ml-2", style: { backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text } },
  790. item.year
  791. )
  792. )
  793. ),
  794. item.subtitle && (
  795. React.createElement("p", { className: "text-sm opacity-80", style: { color: theme.text } }, item.subtitle)
  796. )
  797. )
  798. )
  799. );
  800. }
  801. })
  802. )
  803. );
  804. }
  805. case 'form': {
  806. const formBlock = block as FormBlock;
  807. return <FormBlockComponent block={formBlock} theme={theme} onFormSubmit={onFormSubmit} />;
  808. }
  809. case 'footer': {
  810. const footerBlock = block as FooterBlock;
  811. const footerTextColor = isMobile ? theme.text : (design.sideNavSettings.textColor || theme.text);
  812. if (footerBlock.layout === 'centered') {
  813. return React.createElement("footer", { className: "mt-8 py-8 px-4 text-center space-y-4", style: { backgroundColor: isMobile ? 'rgba(128,128,128,0.1)' : design.sideNavSettings.backgroundColor, color: footerTextColor } },
  814. (footerBlock.navLinks.length > 0 || footerBlock.otherLinks.length > 0) && React.createElement("div", { className: "flex justify-center flex-wrap gap-x-6 gap-y-2" },
  815. [...footerBlock.navLinks, ...footerBlock.otherLinks].map(link =>
  816. React.createElement("a", { key: link.id, href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor } }, link.title)
  817. )
  818. ),
  819. footerBlock.statement && React.createElement("p", { className: "text-xs max-w-2xl mx-auto", style: { opacity: 0.8 } }, footerBlock.statement),
  820. React.createElement("div", { className: "text-xs", style: { opacity: 0.7 } },
  821. React.createElement("p", null, footerBlock.copyrightText),
  822. footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
  823. )
  824. );
  825. }
  826. // Standard Layout
  827. return React.createElement("footer", { className: "mt-8 py-10 px-6", style: { backgroundColor: isMobile ? 'rgba(128,128,128,0.1)' : design.sideNavSettings.backgroundColor, color: footerTextColor } },
  828. React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-8" },
  829. React.createElement("div", { className: "md:col-span-2" },
  830. footerBlock.statement && React.createElement("p", { className: "text-sm", style: { opacity: 0.8 } }, footerBlock.statement)
  831. ),
  832. React.createElement("div", null,
  833. React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Navigation"),
  834. React.createElement("ul", { className: "space-y-2" },
  835. footerBlock.navLinks.map(link => React.createElement("li", { key: link.id }, React.createElement("a", { href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor, opacity: 0.9 } }, link.title)))
  836. )
  837. ),
  838. React.createElement("div", null,
  839. React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Links"),
  840. React.createElement("ul", { className: "space-y-2" },
  841. footerBlock.otherLinks.map(link => React.createElement("li", { key: link.id }, React.createElement("a", { href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor, opacity: 0.9 } }, link.title)))
  842. )
  843. )
  844. ),
  845. React.createElement("div", { className: "mt-8 pt-6 border-t", style: { borderColor: isMobile ? 'rgba(128,128,128,0.2)' : design.sideNavSettings.hoverBackgroundColor, color: footerTextColor, opacity: 0.7 } },
  846. React.createElement("div", { className: "flex flex-col md:flex-row justify-between text-xs" },
  847. React.createElement("p", null, footerBlock.copyrightText),
  848. footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
  849. )
  850. )
  851. );
  852. }
  853. default:
  854. return null;
  855. }
  856. };
  857. if (previewMode === 'personal') {
  858. const avatarUrl = getImageUrl(design.avatarSource);
  859. if (deviceView === 'mobile') {
  860. const chatButtonStyle: React.CSSProperties = {
  861. animationDuration: '3s',
  862. };
  863. if (design.buttonStyle === 'filled') {
  864. chatButtonStyle.backgroundColor = '#10b981';
  865. chatButtonStyle.color = '#ffffff';
  866. } else {
  867. chatButtonStyle.backgroundColor = 'transparent';
  868. chatButtonStyle.color = '#10b981';
  869. chatButtonStyle.borderColor = '#10b981';
  870. }
  871. return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
  872. React.createElement("div", {
  873. key: "personal-preview-screen",
  874. ref: scrollContainerRef,
  875. className: "relative w-[375px] h-[812px] max-h-full max-w-full overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl shadow-xl " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
  876. style: getBackgroundStyle(design.backgroundType, design.backgroundValue)
  877. },
  878. React.createElement("div", { className: "w-full p-4 text-center flex flex-col min-h-full" },
  879. React.createElement("div", { className: "flex-grow" },
  880. avatarUrl && React.createElement("img", { src: avatarUrl, alt: "Avatar", className: "w-32 h-32 rounded-full mx-auto mt-8 mb-6 border-4 border-white dark:border-gray-700 shadow-lg" }),
  881. chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
  882. onClick: () => setIsChatOpen(true),
  883. className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
  884. style: chatButtonStyle
  885. } as any, "Chat with me"),
  886. React.createElement("div", { className: "space-y-4" },
  887. visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
  888. return React.createElement("div", { key: block.id }, renderBlock(block));
  889. })
  890. )
  891. ),
  892. React.createElement("div", { className: "flex-shrink-0 -mx-4 -mb-4" },
  893. visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block)))
  894. )
  895. ),
  896. chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
  897. chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
  898. React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
  899. )
  900. );
  901. }
  902. // PC View for Personal
  903. const chatButtonStyle: React.CSSProperties = {
  904. animationDuration: '3s',
  905. };
  906. if (design.buttonStyle === 'filled') {
  907. chatButtonStyle.backgroundColor = '#10b981';
  908. chatButtonStyle.color = '#ffffff';
  909. } else {
  910. chatButtonStyle.backgroundColor = 'transparent';
  911. chatButtonStyle.color = '#10b981';
  912. chatButtonStyle.borderColor = '#10b981';
  913. }
  914. return React.createElement("div", {
  915. key: "personal-preview-pc",
  916. ref: scrollContainerRef,
  917. className: "absolute inset-0 overflow-y-auto " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
  918. style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
  919. },
  920. React.createElement("div", { className: "w-full max-w-md mx-auto py-8 px-4" },
  921. React.createElement("div", null,
  922. avatarUrl && React.createElement("img", { src: avatarUrl, alt: "Avatar", className: "w-32 h-32 rounded-full mx-auto mb-6 border-4 border-white dark:border-gray-700 shadow-lg" })
  923. ),
  924. chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
  925. onClick: () => setIsChatOpen(true),
  926. className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
  927. style: chatButtonStyle
  928. } as any, "Chat with me"),
  929. React.createElement("div", { className: "space-y-4" },
  930. visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
  931. return React.createElement("div", { key: block.id }, renderBlock(block));
  932. })
  933. )
  934. ),
  935. visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block))),
  936. chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
  937. chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
  938. React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
  939. );
  940. }
  941. // Enterprise Mode
  942. if (deviceView === 'mobile') {
  943. return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
  944. React.createElement("div", {
  945. key: "enterprise-preview-screen",
  946. className: "relative w-[375px] h-[812px] max-h-full max-w-full bg-white dark:bg-gray-900 rounded-2xl shadow-xl overflow-hidden " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
  947. style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
  948. },
  949. React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: deviceView, scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings }),
  950. React.createElement("div", {
  951. ref: scrollContainerRef,
  952. className: "h-full w-full overflow-y-auto scroll-smooth"
  953. },
  954. React.createElement("div", { className: "flex flex-col min-h-full" },
  955. React.createElement("div", { className: "p-4 flex-grow" },
  956. design.bannerSettings.type !== 'none' && (
  957. React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
  958. ),
  959. React.createElement("div", { className: "space-y-4" },
  960. visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
  961. return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
  962. })
  963. )
  964. ),
  965. React.createElement("div", { className: "flex-shrink-0" },
  966. visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
  967. )
  968. )
  969. ),
  970. chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
  971. React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
  972. )
  973. );
  974. }
  975. // PC View for Enterprise
  976. const navFloatStyle = design.sideNavSettings.navFloatStyle || 'normal';
  977. const navBackgroundStyle = design.sideNavSettings.navBackgroundStyle || 'compact';
  978. const navContent = React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: 'pc', scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings });
  979. let navColumn;
  980. if (navBackgroundStyle === 'full') {
  981. const columnClasses = "w-64 flex-shrink-0";
  982. const columnStyle = { backgroundColor: design.sideNavSettings.backgroundColor };
  983. let contentWrapperClasses = 'w-full';
  984. if (navFloatStyle === 'normal') contentWrapperClasses += ' pt-8';
  985. else if (navFloatStyle === 'top') contentWrapperClasses += ' sticky top-8';
  986. else if (navFloatStyle === 'center') contentWrapperClasses += ' sticky top-0 h-screen flex flex-col justify-center';
  987. navColumn = React.createElement("div", { className: columnClasses, style: columnStyle }, React.createElement("div", { className: contentWrapperClasses }, navContent));
  988. } else {
  989. let columnClasses = 'w-64 flex-shrink-0';
  990. if (navFloatStyle === 'normal') columnClasses += ' pt-8';
  991. else if (navFloatStyle === 'top') columnClasses += ' sticky top-8 self-start';
  992. else if (navFloatStyle === 'center') columnClasses += ' sticky top-0 h-screen flex flex-col justify-center';
  993. navColumn = React.createElement("div", { className: columnClasses }, navContent);
  994. }
  995. return React.createElement("div", {
  996. key: "enterprise-preview",
  997. className: "h-full relative " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
  998. style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
  999. },
  1000. React.createElement("div", {
  1001. ref: scrollContainerRef,
  1002. className: "h-full w-full overflow-y-auto scroll-smooth"
  1003. },
  1004. React.createElement("div", {
  1005. className: "flex flex-col min-h-full"
  1006. },
  1007. React.createElement("div", {
  1008. className: "mx-auto flex items-stretch gap-8 px-8 flex-grow",
  1009. style: { maxWidth: '1344px' }
  1010. },
  1011. navColumn,
  1012. React.createElement("main", {
  1013. className: "flex-1 min-w-0 py-8"
  1014. },
  1015. design.bannerSettings.type !== 'none' && (
  1016. React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + (design.bannerSettings.width === 'contained' ? '' : '-mx-8') + " " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
  1017. ),
  1018. React.createElement("div", { className: "space-y-4" },
  1019. visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
  1020. return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
  1021. })
  1022. )
  1023. )
  1024. ),
  1025. React.createElement("div", { className: "flex-shrink-0" },
  1026. visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
  1027. )
  1028. )
  1029. ),
  1030. chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
  1031. React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
  1032. );
  1033. };