PageBuilder.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import * as React from 'react';
  2. import { PageSettings, AIGCVideo, AIGCArticle, FormSubmission } from '../types';
  3. import LinkEditor from './LinkEditor';
  4. import DesignEditor from './DesignEditor';
  5. import { Preview } from './Preview';
  6. import { Icon } from './ui/Icon';
  7. import { useTranslation } from '../hooks/useI18n';
  8. interface PageBuilderProps {
  9. initialTab: 'link' | 'media' | 'design';
  10. pageSettings: PageSettings;
  11. onUpdate: (newSettings: PageSettings) => void;
  12. aigcVideos: AIGCVideo[];
  13. aigcArticles: AIGCArticle[];
  14. onOpenShareModal: () => void;
  15. onFormSubmit: (submission: FormSubmission) => void;
  16. }
  17. export const PageBuilder: React.FC<PageBuilderProps> = ({ initialTab, pageSettings, onUpdate, aigcVideos, aigcArticles, onOpenShareModal, onFormSubmit }) => {
  18. const { t } = useTranslation();
  19. const [previewMode, setPreviewMode] = React.useState<'personal' | 'enterprise'>('personal');
  20. const [deviceView, setDeviceView] = React.useState<'pc' | 'mobile'>('pc');
  21. const [isPreviewFullScreen, setIsPreviewFullScreen] = React.useState(false);
  22. const handleBlocksUpdate = (newBlocks: any) => {
  23. onUpdate({ ...pageSettings, blocks: newBlocks });
  24. };
  25. const handleDesignUpdate = (newDesign: any) => {
  26. onUpdate({ ...pageSettings, design: newDesign });
  27. };
  28. const renderEditor = () => {
  29. switch (initialTab) {
  30. case 'link':
  31. return <LinkEditor blocks={pageSettings.blocks} setBlocks={handleBlocksUpdate} aigcVideos={aigcVideos} aigcArticles={aigcArticles} />
  32. case 'media':
  33. return <div className="p-8 text-gray-500 dark:text-gray-400">Media content editor will be here.</div>
  34. case 'design':
  35. return <DesignEditor design={pageSettings.design} setDesign={handleDesignUpdate} />
  36. default:
  37. return null
  38. }
  39. };
  40. if (isPreviewFullScreen) {
  41. return (
  42. <div className="fixed inset-0 bg-gray-100 dark:bg-gray-900 z-50 flex flex-col">
  43. <div className="flex-shrink-0 bg-white dark:bg-gray-800 p-2 border-b border-gray-200 dark:border-gray-700 flex justify-end items-center">
  44. <button onClick={() => setIsPreviewFullScreen(false)} className="flex items-center gap-2 bg-brand-primary text-white font-semibold py-2 px-4 rounded-lg hover:bg-brand-secondary transition-colors">
  45. <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
  46. {t('page_builder.exit_fullscreen')}
  47. </button>
  48. </div>
  49. <div className="flex-1 overflow-hidden relative">
  50. {/* FIX: Pass onFormSubmit prop to Preview component to handle form submissions. */}
  51. <Preview
  52. pageSettings={pageSettings}
  53. aigcVideos={aigcVideos}
  54. aigcArticles={aigcArticles}
  55. previewMode={previewMode}
  56. deviceView={deviceView}
  57. onFormSubmit={onFormSubmit}
  58. />
  59. </div>
  60. </div>
  61. );
  62. }
  63. return (
  64. <div className="flex h-full">
  65. <div className="w-1/2 flex flex-col">
  66. <header className="p-6 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
  67. <div>
  68. <h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('page_builder.title')}</h2>
  69. <p className="text-gray-500 dark:text-gray-400 mt-1">{t('page_builder.url_prompt')} <a href="#" className="text-brand-primary hover:underline">greenpage.ai/johndoe</a></p>
  70. </div>
  71. <button onClick={onOpenShareModal} className="flex items-center gap-2 bg-brand-primary text-white font-semibold py-2 px-4 rounded-lg hover:bg-brand-secondary transition-colors">
  72. <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M8.684 13.342C8.886 12.938 9 12.482 9 12s-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 100-6 3 3 0 000 6z" /></Icon>
  73. {t('page_builder.share')}
  74. </button>
  75. </header>
  76. <div className="flex-1 overflow-y-auto p-6">
  77. {renderEditor()}
  78. </div>
  79. </div>
  80. <div className="w-1/2 border-l border-gray-200 dark:border-gray-700 flex flex-col">
  81. <div className="flex-shrink-0 bg-white dark:bg-gray-800 p-2 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
  82. <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
  83. <button onClick={() => setPreviewMode('personal')} className={`px-3 py-1 text-sm rounded-md ${previewMode === 'personal' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{t('page_builder.personal_mode')}</button>
  84. <button onClick={() => setPreviewMode('enterprise')} className={`px-3 py-1 text-sm rounded-md flex items-center gap-2 ${previewMode === 'enterprise' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>
  85. {t('page_builder.enterprise_mode')}
  86. <Icon className='h-4 w-4 text-yellow-400'><path fillRule="evenodd" d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" clipRule="evenodd" /></Icon>
  87. </button>
  88. </div>
  89. <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
  90. <button onClick={() => setDeviceView('pc')} className={`p-2 rounded-md ${deviceView === 'pc' ? 'bg-brand-primary text-white' : ''}`}>
  91. <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></Icon>
  92. </button>
  93. <button onClick={() => setDeviceView('mobile')} className={`p-2 rounded-md ${deviceView === 'mobile' ? 'bg-brand-primary text-white' : ''}`}>
  94. <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M12 18h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" /></Icon>
  95. </button>
  96. <button onClick={() => setIsPreviewFullScreen(true)} className="p-2 rounded-md">
  97. <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" /></Icon>
  98. </button>
  99. </div>
  100. </div>
  101. <div className="flex-1 overflow-hidden relative">
  102. {/* FIX: Pass onFormSubmit prop to Preview component to handle form submissions. */}
  103. <Preview
  104. key={`${previewMode}-${deviceView}`}
  105. pageSettings={pageSettings}
  106. aigcVideos={aigcVideos}
  107. aigcArticles={aigcArticles}
  108. previewMode={previewMode}
  109. deviceView={deviceView}
  110. onFormSubmit={onFormSubmit}
  111. />
  112. </div>
  113. </div>
  114. </div>
  115. );
  116. };