|
@@ -0,0 +1,1135 @@
|
|
|
|
|
+import * as React from 'react';
|
|
|
|
|
+// FIX: Import FormBlock and FormSubmission to handle form rendering and data submission.
|
|
|
|
|
+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';
|
|
|
|
|
+import { Icon } from './ui/Icon';
|
|
|
|
|
+import ChatWidget from './ChatWidget';
|
|
|
|
|
+import { sendChatMessage } from '../services/geminiService';
|
|
|
|
|
+import EnterpriseNav from './EnterpriseNav';
|
|
|
|
|
+import { format, parseISO } from 'date-fns';
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// --- STYLING HELPERS ---
|
|
|
|
|
+
|
|
|
|
|
+const getImageUrl = (source?: MediaSource): string | undefined => {
|
|
|
|
|
+ if (!source) return undefined;
|
|
|
|
|
+ switch (source.type) {
|
|
|
|
|
+ case 'url': return source.value;
|
|
|
|
|
+ case 'file': return source.value.previewUrl;
|
|
|
|
|
+ default: return undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var themePresets: Record<ThemeName, { bg: string, text: string, button: string, buttonText: string }> = {
|
|
|
|
|
+ light: { bg: '#F3F4F6', text: '#1F2937', button: '#FFFFFF', buttonText: '#1F2937' },
|
|
|
|
|
+ dark: { bg: '#1F2937', text: '#FFFFFF', button: '#374151', buttonText: '#FFFFFF' },
|
|
|
|
|
+ synthwave: { bg: '#2D3748', text: '#F0D43A', button: '#EC4899', buttonText: '#FFFFFF' },
|
|
|
|
|
+ retro: { bg: '#FEF3C7', text: '#854D0E', button: '#FBBF24', buttonText: '#000000' },
|
|
|
|
|
+ custom: { bg: '', text: '', button: '', buttonText: '' } // Handled separately
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var socialIconPaths: Record<SocialPlatform, React.ReactElement> = {
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ 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" })
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const enterpriseInfoIcons: Record<EnterpriseInfoIcon, React.ReactElement> = {
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+ 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" })),
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+ 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" />,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var getFontClass = function(font: FontFamily) {
|
|
|
|
|
+ switch(font) {
|
|
|
|
|
+ case 'serif': return 'font-serif';
|
|
|
|
|
+ case 'mono': return 'font-mono';
|
|
|
|
|
+ case 'sans': default: return 'font-sans';
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getFontFamilyValue = (font: FontFamily) => {
|
|
|
|
|
+ switch (font) {
|
|
|
|
|
+ case 'serif': return 'serif';
|
|
|
|
|
+ case 'mono': return 'monospace';
|
|
|
|
|
+ case 'sans': default: return 'sans-serif';
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var getButtonClasses = function(shape: ButtonShape, style: ButtonStyle) {
|
|
|
|
|
+ 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 ';
|
|
|
|
|
+ if(shape === 'pill') classes += 'rounded-full';
|
|
|
|
|
+ else if(shape === 'square') classes += 'rounded-none';
|
|
|
|
|
+ else classes += 'rounded-lg';
|
|
|
|
|
+ if(style === 'outline') classes += ' bg-transparent border-2';
|
|
|
|
|
+ return classes;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var getBackgroundStyle = function(type: BackgroundType, value: string) {
|
|
|
|
|
+ if (type === 'image') return { backgroundImage: "url(" + value + ")", backgroundSize: 'cover', backgroundPosition: 'center' };
|
|
|
|
|
+ if (type === 'color') return { backgroundColor: value };
|
|
|
|
|
+ return {}; // for gradient, we use a class
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var getBackgroundClass = function(type: BackgroundType, value: string) {
|
|
|
|
|
+ return type === 'gradient' ? value : '';
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var getBannerStyle = function(settings: BannerSettings): React.CSSProperties {
|
|
|
|
|
+ var style: React.CSSProperties = { height: settings.height + "px" };
|
|
|
|
|
+ const imageUrl = getImageUrl(settings.imageSource);
|
|
|
|
|
+ if (settings.type === 'image' && imageUrl) {
|
|
|
|
|
+ style.backgroundImage = "url(" + imageUrl + ")";
|
|
|
|
|
+ style.backgroundSize = 'cover';
|
|
|
|
|
+ style.backgroundPosition = 'center';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (settings.type === 'color') {
|
|
|
|
|
+ style = Object.assign({}, style, { backgroundColor: settings.value });
|
|
|
|
|
+ }
|
|
|
|
|
+ return style;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+var getBannerClass = function(settings: BannerSettings) {
|
|
|
|
|
+ var classes = '';
|
|
|
|
|
+ if (settings.type === 'gradient') {
|
|
|
|
|
+ classes += " " + settings.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ return classes;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// --- Child Components ---
|
|
|
|
|
+
|
|
|
|
|
+var ActionModal: React.FC<{
|
|
|
|
|
+ data: { type: 'email' | 'phone'; value: string; label: string; } | null;
|
|
|
|
|
+ onClose: () => void;
|
|
|
|
|
+}> = function(props) {
|
|
|
|
|
+ var data = props.data, onClose = props.onClose;
|
|
|
|
|
+ if (!data) return null;
|
|
|
|
|
+
|
|
|
|
|
+ var isEmail = data.type === 'email';
|
|
|
|
|
+ var actionText = isEmail ? 'Open Mail App' : 'Call';
|
|
|
|
|
+ var actionHref = isEmail ? "mailto:" + data.value : "tel:" + data.value;
|
|
|
|
|
+
|
|
|
|
|
+ var handleCopy = function() {
|
|
|
|
|
+ navigator.clipboard.writeText(data.value).then(function() {
|
|
|
|
|
+ alert((isEmail ? 'Email' : 'Number') + " copied to clipboard!");
|
|
|
|
|
+ onClose();
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center z-[100]", onClick: () => onClose() } as any,
|
|
|
|
|
+ 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,
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold text-lg mb-2 text-gray-900 dark:text-white" }, data.label),
|
|
|
|
|
+ React.createElement("p", { className: "text-gray-600 dark:text-gray-300 mb-6 break-words" }, data.value),
|
|
|
|
|
+ React.createElement("div", { className: "space-y-3" },
|
|
|
|
|
+ React.createElement("button", { onClick: handleCopy, className: "w-full bg-brand-primary text-white font-semibold py-3 rounded-lg" }, "Copy " + (isEmail ? 'Email' : 'Number')),
|
|
|
|
|
+ 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),
|
|
|
|
|
+ React.createElement("button", { onClick: () => onClose(), className: "mt-4 w-full text-sm text-gray-500 dark:text-gray-400" } as any, "Cancel")
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+var PersonalChatOverlay: React.FC<{ isOpen: boolean; onClose: () => void; settings: ChatWidgetSettings }> = function(props) {
|
|
|
|
|
+ var isOpen = props.isOpen, onClose = props.onClose, settings = props.settings;
|
|
|
|
|
+ var messagesState = React.useState<ChatMessage[]>([]);
|
|
|
|
|
+ var messages = messagesState[0];
|
|
|
|
|
+ var setMessages = messagesState[1];
|
|
|
|
|
+ var userInputState = React.useState('');
|
|
|
|
|
+ var userInput = userInputState[0];
|
|
|
|
|
+ var setUserInput = userInputState[1];
|
|
|
|
|
+ var isLoadingState = React.useState(false);
|
|
|
|
|
+ var isLoading = isLoadingState[0];
|
|
|
|
|
+ var setIsLoading = isLoadingState[1];
|
|
|
|
|
+ var isRecordingState = React.useState(false);
|
|
|
|
|
+ var isRecording = isRecordingState[0];
|
|
|
|
|
+ var setIsRecording = isRecordingState[1];
|
|
|
|
|
+
|
|
|
|
|
+ var messagesEndRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
|
+ var recognitionRef = React.useRef<any>(null);
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(function() {
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+ var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
|
|
+ if (SpeechRecognition) {
|
|
|
|
|
+ recognitionRef.current = new SpeechRecognition();
|
|
|
|
|
+ recognitionRef.current.continuous = false;
|
|
|
|
|
+ recognitionRef.current.lang = 'en-US';
|
|
|
|
|
+ recognitionRef.current.interimResults = false;
|
|
|
|
|
+ recognitionRef.current.maxAlternatives = 1;
|
|
|
|
|
+
|
|
|
|
|
+ recognitionRef.current.onresult = function(event: any) {
|
|
|
|
|
+ var transcript = event.results[0][0].transcript;
|
|
|
|
|
+ setUserInput(transcript);
|
|
|
|
|
+ setIsRecording(false);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ recognitionRef.current.onerror = function(event: any) {
|
|
|
|
|
+ console.error('Speech recognition error:', event.error);
|
|
|
|
|
+ setIsRecording(false);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ recognitionRef.current.onend = function() {
|
|
|
|
|
+ setIsRecording(false);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(function() {
|
|
|
|
|
+ if (isOpen && messages.length === 0) {
|
|
|
|
|
+ var initialMessage: ChatMessage = { id: "msg-ai-" + Date.now(), sender: 'ai', text: 'Hello! How can I help you today?' };
|
|
|
|
|
+ setMessages([initialMessage]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [isOpen, messages.length]);
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(function() {
|
|
|
|
|
+ if (messagesEndRef.current) {
|
|
|
|
|
+ messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [messages, isLoading]);
|
|
|
|
|
+
|
|
|
|
|
+ var handleSendMessage = React.useCallback(function() {
|
|
|
|
|
+ if (!userInput.trim()) return;
|
|
|
|
|
+ var userMessage: ChatMessage = { id: "msg-user-" + Date.now(), sender: 'user', text: userInput };
|
|
|
|
|
+ setMessages(function(prev) { return prev.concat([userMessage]); });
|
|
|
|
|
+ var messageToSend = userInput;
|
|
|
|
|
+ setUserInput('');
|
|
|
|
|
+ setIsLoading(true);
|
|
|
|
|
+
|
|
|
|
|
+ sendChatMessage(messageToSend).then(function(aiResponse) {
|
|
|
|
|
+ setMessages(function(prev) { return prev.concat([aiResponse]); });
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [userInput]);
|
|
|
|
|
+
|
|
|
|
|
+ var toggleRecording = function() {
|
|
|
|
|
+ if (!recognitionRef.current) {
|
|
|
|
|
+ alert("Speech recognition is not supported by your browser.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isRecording) {
|
|
|
|
|
+ recognitionRef.current.stop();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ recognitionRef.current.start();
|
|
|
|
|
+ }
|
|
|
|
|
+ setIsRecording(!isRecording);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("div", {
|
|
|
|
|
+ className: "absolute inset-0 bg-black/30 z-50 transition-opacity duration-300 " + (isOpen ? "opacity-100" : "opacity-0 pointer-events-none"),
|
|
|
|
|
+ onClick: function() { return onClose(); }
|
|
|
|
|
+ } as any,
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ 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"),
|
|
|
|
|
+ style: { backgroundColor: settings.panelBackgroundColor },
|
|
|
|
|
+ onClick: function(e) { e.stopPropagation(); }
|
|
|
|
|
+ } as any,
|
|
|
|
|
+ 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,
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold text-lg" }, "AI Assistant"),
|
|
|
|
|
+ React.createElement("button", { onClick: () => onClose(), style: { color: settings.headerTextColor } } as any,
|
|
|
|
|
+ React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-1 p-4 overflow-y-auto space-y-4" } as any,
|
|
|
|
|
+ messages.map(function(msg) {
|
|
|
|
|
+ return React.createElement("div", { key: msg.id, className: "flex items-start gap-3 " + (msg.sender === 'user' ? 'justify-end' : '') },
|
|
|
|
|
+ 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 } }, "🤖"),
|
|
|
|
|
+ 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,
|
|
|
|
|
+ React.createElement("p", null, msg.text)
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }),
|
|
|
|
|
+ isLoading && React.createElement("div", { className: "flex items-start gap-3" },
|
|
|
|
|
+ React.createElement("div", { className: "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0", style: { backgroundColor: settings.aiMessageBackgroundColor } }, "🤖"),
|
|
|
|
|
+ React.createElement("div", { className: "max-w-xs p-3 rounded-lg", style: { backgroundColor: settings.aiMessageBackgroundColor } } as any,
|
|
|
|
|
+ React.createElement("div", { className: "flex items-center space-x-1" },
|
|
|
|
|
+ React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-0", style: { backgroundColor: settings.aiMessageTextColor } }),
|
|
|
|
|
+ React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-150", style: { backgroundColor: settings.aiMessageTextColor } }),
|
|
|
|
|
+ React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-300", style: { backgroundColor: settings.aiMessageTextColor } })
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { ref: messagesEndRef })
|
|
|
|
|
+ ),
|
|
|
|
|
+ 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,
|
|
|
|
|
+ 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,
|
|
|
|
|
+ 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" }) })
|
|
|
|
|
+ ),
|
|
|
|
|
+ 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),
|
|
|
|
|
+ React.createElement("button", { onClick: handleSendMessage, disabled: isLoading, className: "p-2 rounded-full text-white disabled:opacity-50", style: { backgroundColor: settings.headerBackgroundColor } } as any,
|
|
|
|
|
+ React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 10l7-7m0 0l7 7m-7-7v18" }) })
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// --- MAIN PREVIEW COMPONENT ---
|
|
|
|
|
+
|
|
|
|
|
+interface PreviewProps {
|
|
|
|
|
+ pageSettings: PageSettings;
|
|
|
|
|
+ aigcVideos: AIGCVideo[];
|
|
|
|
|
+ aigcArticles: AIGCArticle[];
|
|
|
|
|
+ previewMode: 'personal' | 'enterprise';
|
|
|
|
|
+ deviceView: 'pc' | 'mobile';
|
|
|
|
|
+ onFormSubmit: (submission: FormSubmission) => void;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const FormBlockComponent: React.FC<{
|
|
|
|
|
+ block: FormBlock;
|
|
|
|
|
+ theme: { text: string; button: string; buttonText: string };
|
|
|
|
|
+ onFormSubmit: (submission: FormSubmission) => void;
|
|
|
|
|
+}> = ({ block, theme, onFormSubmit }) => {
|
|
|
|
|
+ const [formData, setFormData] = React.useState<Record<string, string>>({});
|
|
|
|
|
+ const inputClasses = "w-full p-2 rounded-md bg-white/10 border border-white/20 focus:outline-none focus:ring-1 focus:ring-white";
|
|
|
|
|
+
|
|
|
|
|
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
|
|
|
+ setFormData({
|
|
|
|
|
+ ...formData,
|
|
|
|
|
+ [e.target.name]: e.target.value,
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSubmit = (e: React.FormEvent) => {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const submission: FormSubmission = {
|
|
|
|
|
+ id: `sub_${Date.now()}`,
|
|
|
|
|
+ formId: block.id,
|
|
|
|
|
+ visitorId: 'visitor_123', // mock visitor id
|
|
|
|
|
+ timestamp: new Date().toISOString(),
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ };
|
|
|
|
|
+ onFormSubmit(submission);
|
|
|
|
|
+ // Reset form
|
|
|
|
|
+ const emptyData: Record<string, string> = {};
|
|
|
|
|
+ block.fields.filter(f => f.enabled).forEach(f => emptyData[f.id] = '');
|
|
|
|
|
+ if (block.purposeOptions.length > 0) {
|
|
|
|
|
+ emptyData['purpose'] = block.purposeOptions[0].label;
|
|
|
|
|
+ }
|
|
|
|
|
+ setFormData(emptyData);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(() => {
|
|
|
|
|
+ const initialData: Record<string, string> = {};
|
|
|
|
|
+ if (block.purposeOptions.length > 0) {
|
|
|
|
|
+ initialData['purpose'] = block.purposeOptions[0].label;
|
|
|
|
|
+ }
|
|
|
|
|
+ setFormData(initialData);
|
|
|
|
|
+ }, [block.purposeOptions]);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div style={{ backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text }} className="p-6 rounded-lg">
|
|
|
|
|
+ <h3 className="text-xl font-bold mb-2 text-center" style={{ color: theme.text }}>{block.title}</h3>
|
|
|
|
|
+ <p className="text-sm opacity-80 mb-6 text-center" style={{ color: theme.text }}>{block.description}</p>
|
|
|
|
|
+ <form onSubmit={handleSubmit} className="space-y-4">
|
|
|
|
|
+ {block.fields.filter(f => f.enabled).map(field => (
|
|
|
|
|
+ <div key={field.id} className="flex items-center gap-4">
|
|
|
|
|
+ <label htmlFor={field.id} className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
|
|
|
|
|
+ {field.label} {field.required && <span className="text-red-500">*</span>}
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <input
|
|
|
|
|
+ type={field.id === 'email' ? 'email' : field.id === 'phone' ? 'tel' : 'text'}
|
|
|
|
|
+ id={field.id}
|
|
|
|
|
+ name={field.id}
|
|
|
|
|
+ required={field.required}
|
|
|
|
|
+ onChange={handleInputChange}
|
|
|
|
|
+ value={formData[field.id] || ''}
|
|
|
|
|
+ className={`${inputClasses} flex-1`}
|
|
|
|
|
+ style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ {block.purposeOptions.length > 0 && (
|
|
|
|
|
+ <div className="flex items-center gap-4">
|
|
|
|
|
+ <label htmlFor="purpose" className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
|
|
|
|
|
+ Purpose
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <select
|
|
|
|
|
+ id="purpose"
|
|
|
|
|
+ name="purpose"
|
|
|
|
|
+ onChange={handleInputChange}
|
|
|
|
|
+ value={formData['purpose'] || block.purposeOptions[0].label}
|
|
|
|
|
+ className={`${inputClasses} flex-1`}
|
|
|
|
|
+ style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
|
|
|
|
|
+ >
|
|
|
|
|
+ {block.purposeOptions.map(opt => <option key={opt.id} value={opt.label} style={{backgroundColor: '#374151'}}>{opt.label}</option>)}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <button type="submit" style={{ backgroundColor: theme.button, color: theme.buttonText }} className="w-full p-3 font-semibold rounded-lg">
|
|
|
|
|
+ {block.submitButtonText}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+export var Preview: React.FC<PreviewProps> = function(props) {
|
|
|
|
|
+ var { pageSettings, aigcVideos, aigcArticles, previewMode, deviceView, onFormSubmit } = props;
|
|
|
|
|
+ var design = pageSettings.design;
|
|
|
|
|
+ var blocks = pageSettings.blocks;
|
|
|
|
|
+ var theme = design.theme === 'custom'
|
|
|
|
|
+ ? {
|
|
|
|
|
+ bg: design.customThemeColors.background,
|
|
|
|
|
+ text: design.customThemeColors.text,
|
|
|
|
|
+ button: design.customThemeColors.button,
|
|
|
|
|
+ buttonText: design.customThemeColors.buttonText
|
|
|
|
|
+ }
|
|
|
|
|
+ : themePresets[design.theme];
|
|
|
|
|
+
|
|
|
|
|
+ if (design.fontColor) {
|
|
|
|
|
+ theme.text = design.fontColor;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var actionModalDataState = React.useState<{ type: 'email' | 'phone'; value: string; label: string; } | null>(null);
|
|
|
|
|
+ var actionModalData = actionModalDataState[0];
|
|
|
|
|
+ var setActionModalData = actionModalDataState[1];
|
|
|
|
|
+
|
|
|
|
|
+ var isChatOpenState = React.useState(false);
|
|
|
|
|
+ var isChatOpen = isChatOpenState[0];
|
|
|
|
|
+ var setIsChatOpen = isChatOpenState[1];
|
|
|
|
|
+
|
|
|
|
|
+ var scrollContainerRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
|
+ const chatBlock = blocks.find(b => b.type === 'chat' && b.visible) as ChatBlock | undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const handleViewGeneratedArticle = (article: AIGCArticle) => {
|
|
|
|
|
+ const isDark = design.theme === 'dark' || design.theme === 'synthwave';
|
|
|
|
|
+ const styles = `
|
|
|
|
|
+ body {
|
|
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ color: ${isDark ? '#e5e7eb' : '#1f2937'};
|
|
|
|
|
+ background-color: ${isDark ? '#111827' : '#ffffff'};
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 40px auto;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .prose { max-width: 65ch; margin: 0 auto; }
|
|
|
|
|
+ .prose h1, .prose h2, .prose h3 { color: inherit; }
|
|
|
|
|
+ .prose a { color: #10b981; }
|
|
|
|
|
+ .prose img { max-width: 100%; height: auto; border-radius: 8px; }
|
|
|
|
|
+ .prose .lead { font-size: 1.25em; color: ${isDark ? '#9ca3af' : '#6b7280'}; }
|
|
|
|
|
+ `;
|
|
|
|
|
+
|
|
|
|
|
+ const articleHtml = `
|
|
|
|
|
+ <!DOCTYPE html>
|
|
|
|
|
+ <html lang="en">
|
|
|
|
|
+ <head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
+ <title>${article.title}</title>
|
|
|
|
|
+ <style>${styles}</style>
|
|
|
|
|
+ </head>
|
|
|
|
|
+ <body>
|
|
|
|
|
+ <article class="prose">
|
|
|
|
|
+ <h1>${article.title}</h1>
|
|
|
|
|
+ <p class="lead">${article.summary}</p>
|
|
|
|
|
+ <hr>
|
|
|
|
|
+ <div>${article.content}</div>
|
|
|
|
|
+ </article>
|
|
|
|
|
+ </body>
|
|
|
|
|
+ </html>
|
|
|
|
|
+ `;
|
|
|
|
|
+ const newWindow = window.open();
|
|
|
|
|
+ if (newWindow) {
|
|
|
|
|
+ newWindow.document.write(articleHtml);
|
|
|
|
|
+ newWindow.document.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(function() {
|
|
|
|
|
+ if (scrollContainerRef.current) {
|
|
|
|
|
+ scrollContainerRef.current.scrollTop = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [previewMode, deviceView]);
|
|
|
|
|
+
|
|
|
|
|
+ React.useEffect(function() {
|
|
|
|
|
+ if (isChatOpen) {
|
|
|
|
|
+ document.body.style.overflow = 'hidden';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ document.body.style.overflow = 'unset';
|
|
|
|
|
+ }
|
|
|
|
|
+ return function() {
|
|
|
|
|
+ document.body.style.overflow = 'unset';
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [isChatOpen]);
|
|
|
|
|
+
|
|
|
|
|
+ var visibleBlocks = blocks.filter(function(b) { return b.visible; });
|
|
|
|
|
+
|
|
|
|
|
+ var renderBlock = function(block: Block) {
|
|
|
|
|
+ var isMobile = deviceView === 'mobile';
|
|
|
|
|
+ const lineClampStyle = (lines: number): React.CSSProperties => ({
|
|
|
|
|
+ display: '-webkit-box',
|
|
|
|
|
+ WebkitLineClamp: lines,
|
|
|
|
|
+ WebkitBoxOrient: 'vertical',
|
|
|
|
|
+ overflow: 'hidden',
|
|
|
|
|
+ textOverflow: 'ellipsis',
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ switch (block.type) {
|
|
|
|
|
+ case 'header':
|
|
|
|
|
+ var headerBlock = block as HeaderBlock;
|
|
|
|
|
+ return React.createElement("h2", { className: "text-2xl font-bold pt-4", style: { color: theme.text, textAlign: headerBlock.titleAlignment } }, headerBlock.text);
|
|
|
|
|
+
|
|
|
|
|
+ case 'link':
|
|
|
|
|
+ var linkBlock = block as LinkBlock;
|
|
|
|
|
+ return (
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ linkBlock.iconUrl && React.createElement("img", { src: linkBlock.iconUrl, alt: "", className: "w-6 h-6 rounded-md" }),
|
|
|
|
|
+ React.createElement("span", { className: "flex-1" }, linkBlock.title)
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ case 'chat': {
|
|
|
|
|
+ const currentChatBlock = block as ChatBlock;
|
|
|
|
|
+ if (previewMode === 'personal' && currentChatBlock.layout !== 'button') return null;
|
|
|
|
|
+ if (currentChatBlock.layout === 'widget') return null;
|
|
|
|
|
+
|
|
|
|
|
+ const chatButtonStyle: React.CSSProperties = {};
|
|
|
|
|
+ if (design.buttonStyle === 'filled') {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = '#10b981';
|
|
|
|
|
+ chatButtonStyle.color = '#ffffff';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = 'transparent';
|
|
|
|
|
+ chatButtonStyle.color = '#10b981';
|
|
|
|
|
+ chatButtonStyle.borderColor = '#10b981';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("button", {
|
|
|
|
|
+ onClick: () => setIsChatOpen(true),
|
|
|
|
|
+ className: getButtonClasses(design.buttonShape, design.buttonStyle) + ' h-16',
|
|
|
|
|
+ style: chatButtonStyle
|
|
|
|
|
+ } as any, "Chat with me");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case 'social':
|
|
|
|
|
+ var socialBlock = block as SocialBlock;
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: "flex justify-center gap-4" },
|
|
|
|
|
+ socialBlock.links.map(function(link) {
|
|
|
|
|
+ var icon = socialIconPaths[link.platform];
|
|
|
|
|
+ return React.createElement("a", { key: link.id, href: link.url, target: "_blank", rel: "noopener noreferrer", style: { color: theme.text }, className: "hover:opacity-75" },
|
|
|
|
|
+ React.createElement("svg", {
|
|
|
|
|
+ className: "h-8 w-8",
|
|
|
|
|
+ fill: "currentColor",
|
|
|
|
|
+ viewBox: "0 0 24 24"
|
|
|
|
|
+ }, icon)
|
|
|
|
|
+ );
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ case 'video':
|
|
|
|
|
+ var videoBlock = block as VideoBlock;
|
|
|
|
|
+ var videoLayoutClass = videoBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: videoLayoutClass },
|
|
|
|
|
+ videoBlock.sources.map(function(source, index) {
|
|
|
|
|
+ var video = source.type === 'aigc' ? aigcVideos.filter(function(v) { return v.id === source.videoId; })[0] : null;
|
|
|
|
|
+ var url = source.type === 'url' ? source.value : (video && video.videoUrl);
|
|
|
|
|
+ if (!url) return null;
|
|
|
|
|
+ return React.createElement("video", { key: index, src: url, controls: true, className: "w-full rounded-lg aspect-video" });
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ case 'image':
|
|
|
|
|
+ var imageBlock = block as ImageBlock;
|
|
|
|
|
+ var imageLayoutClass = imageBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: imageLayoutClass },
|
|
|
|
|
+ imageBlock.sources.map(function(source, index) {
|
|
|
|
|
+ var url = source.type === 'url' ? source.value : (source.type === 'file' ? source.value.previewUrl : '');
|
|
|
|
|
+ return React.createElement("img", { key: index, src: url, alt: "", className: "w-full rounded-lg object-cover aspect-video" });
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ case 'text':
|
|
|
|
|
+ var textBlock = block as TextBlock;
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("p", { style: {
|
|
|
|
|
+ color: textBlock.fontColor || theme.text,
|
|
|
|
|
+ fontSize: textBlock.fontSize,
|
|
|
|
|
+ textAlign: textBlock.textAlign,
|
|
|
|
|
+ fontWeight: textBlock.isBold ? 'bold' : 'normal',
|
|
|
|
|
+ fontStyle: textBlock.isItalic ? 'italic' : 'normal'
|
|
|
|
|
+ } },
|
|
|
|
|
+ textBlock.content
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ case 'enterprise_info': {
|
|
|
|
|
+ const infoBlock = block as EnterpriseInfoBlock;
|
|
|
|
|
+ if (!infoBlock.items || infoBlock.items.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ const isCentered = infoBlock.alignment === 'center';
|
|
|
|
|
+ const containerClasses = isCentered ? 'flex flex-col items-center text-center gap-6' : 'space-y-4';
|
|
|
|
|
+ const itemClasses = isCentered ? 'flex flex-col items-center gap-2' : 'flex items-start gap-4';
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: `p-4 rounded-lg`, style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
|
|
|
|
|
+ React.createElement("div", { className: containerClasses },
|
|
|
|
|
+ infoBlock.items.map(item => (
|
|
|
|
|
+ React.createElement("div", { key: item.id, className: itemClasses },
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ React.createElement(Icon, { className: "h-6 w-6", children: enterpriseInfoIcons[item.icon] })
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "min-w-0" },
|
|
|
|
|
+ React.createElement("p", { className: "text-sm font-semibold uppercase tracking-wider", style: { color: theme.text, opacity: 0.7 } }, item.label),
|
|
|
|
|
+ React.createElement("p", { className: "font-semibold text-base break-words", style: { color: theme.text } }, item.value)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ))
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'news': {
|
|
|
|
|
+ const newsBlock = block as NewsBlock;
|
|
|
|
|
+ const isGrid = newsBlock.layout === 'grid';
|
|
|
|
|
+ const containerClass = isGrid ? 'grid grid-cols-1 md:grid-cols-2 gap-4' : 'space-y-4';
|
|
|
|
|
+
|
|
|
|
|
+ const extractFirstImageUrl = (htmlContent: string): string | null => {
|
|
|
|
|
+ if (typeof DOMParser === 'undefined') return null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const doc = new DOMParser().parseFromString(htmlContent, 'text/html');
|
|
|
|
|
+ const img = doc.querySelector('img');
|
|
|
|
|
+ return img ? img.src : null;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("Error parsing HTML for image extraction", e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (newsBlock.source === 'custom') {
|
|
|
|
|
+ const customItems = newsBlock.customItems || [];
|
|
|
|
|
+ if (customItems.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("div", { className: containerClass },
|
|
|
|
|
+ customItems.map(item => (
|
|
|
|
|
+ React.createElement("a", {
|
|
|
|
|
+ key: item.id,
|
|
|
|
|
+ href: item.url,
|
|
|
|
|
+ target: "_blank",
|
|
|
|
|
+ rel: "noopener noreferrer",
|
|
|
|
|
+ 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`
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm text-brand-primary truncate mt-1" }, item.url),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, item.summary)
|
|
|
|
|
+ )
|
|
|
|
|
+ ))
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const articlesToShow = (newsBlock.articleIds || [])
|
|
|
|
|
+ .map(id => aigcArticles.find(a => a.id === id))
|
|
|
|
|
+ .filter((a): a is AIGCArticle => !!a);
|
|
|
|
|
+
|
|
|
|
|
+ if (articlesToShow.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("div", { className: containerClass },
|
|
|
|
|
+ articlesToShow.map(article => {
|
|
|
|
|
+ const imageUrl = extractFirstImageUrl(article.content);
|
|
|
|
|
+ const hasUrl = article.sourceType === 'url' && article.sourceUrl;
|
|
|
|
|
+
|
|
|
|
|
+ const linkProps: any = {
|
|
|
|
|
+ key: article.id,
|
|
|
|
|
+ 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`,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (hasUrl) {
|
|
|
|
|
+ linkProps.href = article.sourceUrl;
|
|
|
|
|
+ linkProps.target = "_blank";
|
|
|
|
|
+ linkProps.rel = "noopener noreferrer";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ linkProps.href = "#";
|
|
|
|
|
+ linkProps.onClick = (e: React.MouseEvent) => {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ handleViewGeneratedArticle(article);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const articleContent = isGrid ? (
|
|
|
|
|
+ React.createElement(React.Fragment, null,
|
|
|
|
|
+ imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-full h-32 object-cover" }),
|
|
|
|
|
+ React.createElement("div", { className: "p-4" },
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, article.title),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, article.summary)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ) : ( // List View
|
|
|
|
|
+ React.createElement("div", { className: "flex items-start gap-4 p-4" },
|
|
|
|
|
+ imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-40 h-24 object-cover rounded-md flex-shrink-0" }),
|
|
|
|
|
+ React.createElement("div", { className: "flex-1 min-w-0" },
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold text-lg", style: { ...{ color: theme.text }, ...lineClampStyle(2) } }, article.title),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
|
|
|
|
|
+ React.createElement("p", { className: "text-sm mt-2", style: { ...{ color: theme.text, opacity: 0.9 }, ...lineClampStyle(2) } }, article.summary)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("a", linkProps, articleContent);
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case 'email':
|
|
|
|
|
+ case 'phone':
|
|
|
|
|
+ var actionBlock = block as EmailBlock | PhoneBlock;
|
|
|
|
|
+
|
|
|
|
|
+ var iconPath = actionBlock.type === 'email' ?
|
|
|
|
|
+ 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" }) :
|
|
|
|
|
+ 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" });
|
|
|
|
|
+
|
|
|
|
|
+ var buttonContent = React.createElement("div", {
|
|
|
|
|
+ className: "flex flex-col items-center justify-center text-center"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", { className: "flex items-center gap-2" },
|
|
|
|
|
+ React.createElement(Icon, { className: "h-5 w-5 flex-shrink-0", children: iconPath }),
|
|
|
|
|
+ React.createElement("span", null, actionBlock.label)
|
|
|
|
|
+ ),
|
|
|
|
|
+ actionBlock.displayMode === 'labelAndValue' && React.createElement("span", {
|
|
|
|
|
+ className: "text-xs opacity-80 mt-1 font-normal"
|
|
|
|
|
+ }, actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("button", {
|
|
|
|
|
+ onClick: function() { return setActionModalData({ type: actionBlock.type, value: actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone, label: actionBlock.label }); },
|
|
|
|
|
+ className: getButtonClasses(design.buttonShape, design.buttonStyle),
|
|
|
|
|
+ style: {
|
|
|
|
|
+ backgroundColor: design.buttonStyle === 'filled' ? theme.button : 'transparent',
|
|
|
|
|
+ color: theme.buttonText,
|
|
|
|
|
+ borderColor: theme.buttonText
|
|
|
|
|
+ }
|
|
|
|
|
+ } as any, buttonContent);
|
|
|
|
|
+
|
|
|
|
|
+ case 'map':
|
|
|
|
|
+ var mapBlock = block as MapBlock;
|
|
|
|
|
+ var encodedAddress = encodeURIComponent(mapBlock.address);
|
|
|
|
|
+ var displayStyle = mapBlock.displayStyle || 'interactiveMap';
|
|
|
|
|
+
|
|
|
|
|
+ if (displayStyle === 'imageOverlay') {
|
|
|
|
|
+ var imageUrl = getImageUrl(mapBlock.backgroundImageSource);
|
|
|
|
|
+ return React.createElement("a", {
|
|
|
|
|
+ href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
|
|
|
|
|
+ target: "_blank",
|
|
|
|
|
+ rel: "noopener noreferrer",
|
|
|
|
|
+ className: "block rounded-lg overflow-hidden relative h-48 group"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ className: "absolute inset-0 bg-cover bg-center transition-transform duration-300 group-hover:scale-105",
|
|
|
|
|
+ style: { backgroundImage: "url(" + (imageUrl || ("https://picsum.photos/seed/mapbg-" + mapBlock.id + "/600/400")) + ")" }
|
|
|
|
|
+ }),
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ className: "absolute inset-0 bg-black/60 flex items-end p-4"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("a", {
|
|
|
|
|
+ href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
|
|
|
|
|
+ target: "_blank",
|
|
|
|
|
+ rel: "noopener noreferrer",
|
|
|
|
|
+ className: "block rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:border-brand-primary transition-colors"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ className: "relative h-48 bg-cover bg-center",
|
|
|
|
|
+ style: { backgroundImage: "url(https://picsum.photos/seed/map" + mapBlock.id + "/600/400)" }
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-end p-4" },
|
|
|
|
|
+ React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ case 'pdf':
|
|
|
|
|
+ var pdfBlock = block as PdfBlock;
|
|
|
|
|
+ var source = pdfBlock.source;
|
|
|
|
|
+ var url: string | undefined;
|
|
|
|
|
+ var name: string | undefined;
|
|
|
|
|
+ var previewUrl: string | undefined;
|
|
|
|
|
+
|
|
|
|
|
+ if (source.type === 'file') {
|
|
|
|
|
+ url = '#';
|
|
|
|
|
+ name = source.value.name;
|
|
|
|
|
+ previewUrl = source.value.previewUrl;
|
|
|
|
|
+ } else if (source.type === 'url') {
|
|
|
|
|
+ url = source.value;
|
|
|
|
|
+ try {
|
|
|
|
|
+ var urlParts = new URL(url).pathname.split('/');
|
|
|
|
|
+ name = urlParts[urlParts.length - 1] || "Document.pdf";
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ name = "Document.pdf";
|
|
|
|
|
+ }
|
|
|
|
|
+ previewUrl = 'https://api.iconify.design/mdi:file-pdf-box.svg?color=%23' + (theme.text.replace('#',''));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var isFile = source.type === 'file';
|
|
|
|
|
+
|
|
|
|
|
+ var content = React.createElement("div", { className: "flex items-center gap-4" },
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ React.createElement("div", { className: "flex-1 min-w-0" },
|
|
|
|
|
+ React.createElement("p", { className: "font-semibold truncate", style: { color: theme.text } }, name),
|
|
|
|
|
+ !isFile && url && React.createElement("p", { className: "text-sm text-brand-primary" }, "View PDF")
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (!isFile && url) {
|
|
|
|
|
+ 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);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case 'product': {
|
|
|
|
|
+ const productBlock = block as ProductBlock;
|
|
|
|
|
+ if (productBlock.items.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ const isGrid = productBlock.layout === 'grid';
|
|
|
|
|
+ const containerClass = isGrid ? 'grid grid-cols-2 gap-4' : 'space-y-4';
|
|
|
|
|
+
|
|
|
|
|
+ const cardClass = `block w-full text-left rounded-lg transition-shadow hover:shadow-lg overflow-hidden border`;
|
|
|
|
|
+ const cardBgStyle = {
|
|
|
|
|
+ backgroundColor: theme.button,
|
|
|
|
|
+ borderColor: design.buttonStyle === 'outline' ? theme.buttonText : 'transparent',
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: containerClass },
|
|
|
|
|
+ productBlock.items.map(item => (
|
|
|
|
|
+ React.createElement("a", {
|
|
|
|
|
+ key: item.id,
|
|
|
|
|
+ href: item.url,
|
|
|
|
|
+ target: "_blank",
|
|
|
|
|
+ rel: "noopener noreferrer",
|
|
|
|
|
+ className: cardClass,
|
|
|
|
|
+ style: cardBgStyle
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("img", { src: item.imageUrl, alt: item.title, className: `w-full object-cover ${isGrid ? 'aspect-square' : 'aspect-video'}` }),
|
|
|
|
|
+ React.createElement("div", { className: "p-4" },
|
|
|
|
|
+ React.createElement("h3", { className: "font-bold", style: { color: theme.buttonText } }, item.title),
|
|
|
|
|
+ React.createElement("p", { className: "font-semibold mt-1", style: { color: theme.buttonText, opacity: 0.9 } }, item.price)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ))
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'award': {
|
|
|
|
|
+ const awardBlock = block as AwardBlock;
|
|
|
|
|
+ if (!awardBlock.items || awardBlock.items.length === 0) return null;
|
|
|
|
|
+
|
|
|
|
|
+ const isGrid = awardBlock.layout === 'grid';
|
|
|
|
|
+ const containerClass = isGrid ? `grid ${isMobile ? 'grid-cols-1' : 'grid-cols-2'} gap-4` : 'space-y-4';
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ React.createElement("div", { className: containerClass },
|
|
|
|
|
+ awardBlock.items.map(item => {
|
|
|
|
|
+ const imageUrl = getImageUrl(item.imageSource);
|
|
|
|
|
+
|
|
|
|
|
+ if (isGrid) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ 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)' } },
|
|
|
|
|
+ imageUrl && (
|
|
|
|
|
+ React.createElement("div", { className: "p-1 rounded-full bg-gradient-to-br from-yellow-300 via-amber-400 to-orange-500 flex-shrink-0" },
|
|
|
|
|
+ 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 } })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-1 min-w-0" },
|
|
|
|
|
+ item.year && (
|
|
|
|
|
+ React.createElement("span", { className: "text-sm font-semibold opacity-70", style: { color: theme.text } },
|
|
|
|
|
+ item.year
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("h4", { className: "font-bold text-xl truncate w-full", style: { color: theme.text }, title: item.title }, item.title),
|
|
|
|
|
+ item.subtitle && (
|
|
|
|
|
+ React.createElement("p", { className: "text-base opacity-80 break-words", style: { color: theme.text } }, item.subtitle)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return (
|
|
|
|
|
+ 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)' } },
|
|
|
|
|
+ imageUrl && (
|
|
|
|
|
+ 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" },
|
|
|
|
|
+ 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 } })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-1 min-w-0" },
|
|
|
|
|
+ React.createElement("div", { className: "flex justify-between items-start" },
|
|
|
|
|
+ React.createElement("h4", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
|
|
|
|
|
+ item.year && (
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ item.year
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ item.subtitle && (
|
|
|
|
|
+ React.createElement("p", { className: "text-sm opacity-80", style: { color: theme.text } }, item.subtitle)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'form': {
|
|
|
|
|
+ const formBlock = block as FormBlock;
|
|
|
|
|
+ return <FormBlockComponent block={formBlock} theme={theme} onFormSubmit={onFormSubmit} />;
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'footer': {
|
|
|
|
|
+ const footerBlock = block as FooterBlock;
|
|
|
|
|
+ const footerTextColor = isMobile ? theme.text : (design.sideNavSettings.textColor || theme.text);
|
|
|
|
|
+
|
|
|
|
|
+ if (footerBlock.layout === 'centered') {
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ (footerBlock.navLinks.length > 0 || footerBlock.otherLinks.length > 0) && React.createElement("div", { className: "flex justify-center flex-wrap gap-x-6 gap-y-2" },
|
|
|
|
|
+ [...footerBlock.navLinks, ...footerBlock.otherLinks].map(link =>
|
|
|
|
|
+ React.createElement("a", { key: link.id, href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor } }, link.title)
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ footerBlock.statement && React.createElement("p", { className: "text-xs max-w-2xl mx-auto", style: { opacity: 0.8 } }, footerBlock.statement),
|
|
|
|
|
+ React.createElement("div", { className: "text-xs", style: { opacity: 0.7 } },
|
|
|
|
|
+ React.createElement("p", null, footerBlock.copyrightText),
|
|
|
|
|
+ footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Standard Layout
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-8" },
|
|
|
|
|
+ React.createElement("div", { className: "md:col-span-2" },
|
|
|
|
|
+ footerBlock.statement && React.createElement("p", { className: "text-sm", style: { opacity: 0.8 } }, footerBlock.statement)
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", null,
|
|
|
|
|
+ React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Navigation"),
|
|
|
|
|
+ React.createElement("ul", { className: "space-y-2" },
|
|
|
|
|
+ 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)))
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", null,
|
|
|
|
|
+ React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Links"),
|
|
|
|
|
+ React.createElement("ul", { className: "space-y-2" },
|
|
|
|
|
+ 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)))
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ 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 } },
|
|
|
|
|
+ React.createElement("div", { className: "flex flex-col md:flex-row justify-between text-xs" },
|
|
|
|
|
+ React.createElement("p", null, footerBlock.copyrightText),
|
|
|
|
|
+ footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (previewMode === 'personal') {
|
|
|
|
|
+ const avatarUrl = getImageUrl(design.avatarSource);
|
|
|
|
|
+
|
|
|
|
|
+ if (deviceView === 'mobile') {
|
|
|
|
|
+ const chatButtonStyle: React.CSSProperties = {
|
|
|
|
|
+ animationDuration: '3s',
|
|
|
|
|
+ };
|
|
|
|
|
+ if (design.buttonStyle === 'filled') {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = '#10b981';
|
|
|
|
|
+ chatButtonStyle.color = '#ffffff';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = 'transparent';
|
|
|
|
|
+ chatButtonStyle.color = '#10b981';
|
|
|
|
|
+ chatButtonStyle.borderColor = '#10b981';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ key: "personal-preview-screen",
|
|
|
|
|
+ ref: scrollContainerRef,
|
|
|
|
|
+ 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),
|
|
|
|
|
+ style: getBackgroundStyle(design.backgroundType, design.backgroundValue)
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", { className: "w-full p-4 text-center flex flex-col min-h-full" },
|
|
|
|
|
+ React.createElement("div", { className: "flex-grow" },
|
|
|
|
|
+ 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" }),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
|
|
|
|
|
+ onClick: () => setIsChatOpen(true),
|
|
|
|
|
+ className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
|
|
|
|
|
+ style: chatButtonStyle
|
|
|
|
|
+ } as any, "Chat with me"),
|
|
|
|
|
+ React.createElement("div", { className: "space-y-4" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
|
|
|
|
|
+ return React.createElement("div", { key: block.id }, renderBlock(block));
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-shrink-0 -mx-4 -mb-4" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block)))
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
|
|
|
|
|
+ chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
|
|
|
|
|
+ React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // PC View for Personal
|
|
|
|
|
+ const chatButtonStyle: React.CSSProperties = {
|
|
|
|
|
+ animationDuration: '3s',
|
|
|
|
|
+ };
|
|
|
|
|
+ if (design.buttonStyle === 'filled') {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = '#10b981';
|
|
|
|
|
+ chatButtonStyle.color = '#ffffff';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ chatButtonStyle.backgroundColor = 'transparent';
|
|
|
|
|
+ chatButtonStyle.color = '#10b981';
|
|
|
|
|
+ chatButtonStyle.borderColor = '#10b981';
|
|
|
|
|
+ }
|
|
|
|
|
+ return React.createElement("div", {
|
|
|
|
|
+ key: "personal-preview-pc",
|
|
|
|
|
+ ref: scrollContainerRef,
|
|
|
|
|
+ className: "absolute inset-0 overflow-y-auto " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
|
|
|
|
|
+ style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", { className: "w-full max-w-md mx-auto py-8 px-4" },
|
|
|
|
|
+ React.createElement("div", null,
|
|
|
|
|
+ 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" })
|
|
|
|
|
+ ),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
|
|
|
|
|
+ onClick: () => setIsChatOpen(true),
|
|
|
|
|
+ className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
|
|
|
|
|
+ style: chatButtonStyle
|
|
|
|
|
+ } as any, "Chat with me"),
|
|
|
|
|
+ React.createElement("div", { className: "space-y-4" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
|
|
|
|
|
+ return React.createElement("div", { key: block.id }, renderBlock(block));
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block))),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
|
|
|
|
|
+ chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
|
|
|
|
|
+ React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Enterprise Mode
|
|
|
|
|
+ if (deviceView === 'mobile') {
|
|
|
|
|
+ return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ key: "enterprise-preview-screen",
|
|
|
|
|
+ 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),
|
|
|
|
|
+ style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: deviceView, scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings }),
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ ref: scrollContainerRef,
|
|
|
|
|
+ className: "h-full w-full overflow-y-auto scroll-smooth"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", { className: "flex flex-col min-h-full" },
|
|
|
|
|
+ React.createElement("div", { className: "p-4 flex-grow" },
|
|
|
|
|
+ design.bannerSettings.type !== 'none' && (
|
|
|
|
|
+ React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "space-y-4" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
|
|
|
|
|
+ return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-shrink-0" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
|
|
|
|
|
+ React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // PC View for Enterprise
|
|
|
|
|
+ const navFloatStyle = design.sideNavSettings.navFloatStyle || 'normal';
|
|
|
|
|
+ const navBackgroundStyle = design.sideNavSettings.navBackgroundStyle || 'compact';
|
|
|
|
|
+ const navContent = React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: 'pc', scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings });
|
|
|
|
|
+ let navColumn;
|
|
|
|
|
+
|
|
|
|
|
+ if (navBackgroundStyle === 'full') {
|
|
|
|
|
+ const columnClasses = "w-64 flex-shrink-0";
|
|
|
|
|
+ const columnStyle = { backgroundColor: design.sideNavSettings.backgroundColor };
|
|
|
|
|
+ let contentWrapperClasses = 'w-full';
|
|
|
|
|
+ if (navFloatStyle === 'normal') contentWrapperClasses += ' pt-8';
|
|
|
|
|
+ else if (navFloatStyle === 'top') contentWrapperClasses += ' sticky top-8';
|
|
|
|
|
+ else if (navFloatStyle === 'center') contentWrapperClasses += ' sticky top-0 h-screen flex flex-col justify-center';
|
|
|
|
|
+ navColumn = React.createElement("div", { className: columnClasses, style: columnStyle }, React.createElement("div", { className: contentWrapperClasses }, navContent));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let columnClasses = 'w-64 flex-shrink-0';
|
|
|
|
|
+ if (navFloatStyle === 'normal') columnClasses += ' pt-8';
|
|
|
|
|
+ else if (navFloatStyle === 'top') columnClasses += ' sticky top-8 self-start';
|
|
|
|
|
+ else if (navFloatStyle === 'center') columnClasses += ' sticky top-0 h-screen flex flex-col justify-center';
|
|
|
|
|
+ navColumn = React.createElement("div", { className: columnClasses }, navContent);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return React.createElement("div", {
|
|
|
|
|
+ key: "enterprise-preview",
|
|
|
|
|
+ className: "h-full relative " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
|
|
|
|
|
+ style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ ref: scrollContainerRef,
|
|
|
|
|
+ className: "h-full w-full overflow-y-auto scroll-smooth"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ className: "flex flex-col min-h-full"
|
|
|
|
|
+ },
|
|
|
|
|
+ React.createElement("div", {
|
|
|
|
|
+ className: "mx-auto flex items-stretch gap-8 px-8 flex-grow",
|
|
|
|
|
+ style: { maxWidth: '1344px' }
|
|
|
|
|
+ },
|
|
|
|
|
+ navColumn,
|
|
|
|
|
+ React.createElement("main", {
|
|
|
|
|
+ className: "flex-1 min-w-0 py-8"
|
|
|
|
|
+ },
|
|
|
|
|
+ design.bannerSettings.type !== 'none' && (
|
|
|
|
|
+ React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + (design.bannerSettings.width === 'contained' ? '' : '-mx-8') + " " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "space-y-4" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
|
|
|
|
|
+ return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ React.createElement("div", { className: "flex-shrink-0" },
|
|
|
|
|
+ visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
|
|
|
|
|
+ React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
|
|
|
|
|
+ );
|
|
|
|
|
+};
|