Files
MyClub/DOCS/INTEGRATION_GUIDE.md
Tomáš Dvořák 35d0954afd dev day #62
2025-10-16 17:10:13 +02:00

16 KiB

MyUIbrix Elementor Features - Integration Guide

🔧 Component Integration

This guide shows how to integrate all the new Elementor-style features into your pages.


1. Inline Text Editor Integration

Basic Usage

import InlineTextEditor from '@/components/editor/InlineTextEditor';

// In your component
<InlineTextEditor
  elementId="hero-title"
  initialContent="<h1>Welcome to Our Club</h1>"
  onSave={(newContent) => {
    // Save to state or API
    updateElementContent('hero-title', newContent);
  }}
/>

Advanced Usage with State Management

const [heroTitle, setHeroTitle] = useState('<h1>Welcome</h1>');

<InlineTextEditor
  elementId="hero-title"
  initialContent={heroTitle}
  onSave={(content) => {
    setHeroTitle(content);
    // Persist to backend
    saveToAPI('hero', { title: content });
  }}
/>

Making Existing Elements Editable

// Wrap any text element
<Box data-element="news">
  <InlineTextEditor
    elementId="news-headline"
    initialContent={newsHeadline}
    onSave={handleSaveHeadline}
  />
  
  <InlineTextEditor
    elementId="news-description"
    initialContent={newsDescription}
    onSave={handleSaveDescription}
  />
</Box>

2. Column Layout Manager Integration

Basic Setup

import ColumnLayoutManager from '@/components/editor/ColumnLayoutManager';

const [columns, setColumns] = useState([
  { id: '1', width: '50%', elements: [] },
  { id: '2', width: '50%', elements: [] }
]);

<ColumnLayoutManager
  elementName="hero"
  currentColumns={columns}
  onLayoutChange={(newColumns) => {
    setColumns(newColumns);
    applyLayoutToDOM(newColumns);
  }}
/>

Applying Layout to DOM

const applyLayoutToDOM = (columns: Column[]) => {
  const container = document.querySelector('[data-element="hero"]');
  if (!container) return;
  
  // Clear existing layout
  container.style.display = 'grid';
  container.style.gridTemplateColumns = columns.map(c => c.width).join(' ');
  container.style.gap = '20px';
  
  // Save to backend
  saveLayoutConfig('hero', columns);
};

Responsive Columns

const [columns, setColumns] = useState({
  desktop: [
    { id: '1', width: '33.33%', elements: [] },
    { id: '2', width: '33.33%', elements: [] },
    { id: '3', width: '33.33%', elements: [] }
  ],
  tablet: [
    { id: '1', width: '50%', elements: [] },
    { id: '2', width: '50%', elements: [] }
  ],
  mobile: [
    { id: '1', width: '100%', elements: [] }
  ]
});

// Apply based on viewport
const currentColumns = viewport === 'mobile' 
  ? columns.mobile 
  : viewport === 'tablet' 
    ? columns.tablet 
    : columns.desktop;

<ColumnLayoutManager
  elementName="hero"
  currentColumns={currentColumns}
  onLayoutChange={(newCols) => {
    setColumns(prev => ({
      ...prev,
      [viewport]: newCols
    }));
  }}
/>

3. Custom CSS Editor Integration

Basic Integration

import CustomCSSEditor from '@/components/editor/CustomCSSEditor';

const [customCSS, setCustomCSS] = useState('');

<CustomCSSEditor
  elementName="hero"
  currentCSS={customCSS}
  onCSSChange={(css) => {
    setCustomCSS(css);
    applyCustomCSS('hero', css);
  }}
/>

Applying CSS to Elements

const applyCustomCSS = (elementName: string, css: string) => {
  // Remove existing custom style
  const existingStyle = document.getElementById(`custom-css-${elementName}`);
  if (existingStyle) {
    existingStyle.remove();
  }

  // Apply new CSS
  if (css.trim()) {
    const style = document.createElement('style');
    style.id = `custom-css-${elementName}`;
    style.textContent = `
      [data-element="${elementName}"] {
        ${css}
      }
    `;
    document.head.appendChild(style);
  }
  
  // Save to database
  saveCustomCSS(elementName, css);
};

CSS with Media Queries

const applyResponsiveCSS = (elementName: string, css: Record<string, string>) => {
  const style = document.createElement('style');
  style.id = `custom-css-${elementName}`;
  style.textContent = `
    [data-element="${elementName}"] {
      ${css.desktop || ''}
    }
    
    @media (max-width: 768px) {
      [data-element="${elementName}"] {
        ${css.tablet || ''}
      }
    }
    
    @media (max-width: 480px) {
      [data-element="${elementName}"] {
        ${css.mobile || ''}
      }
    }
  `;
  document.head.appendChild(style);
};

Basic Usage

import ContextualAdminLinks from '@/components/editor/ContextualAdminLinks';

// In your style panel or settings popup
<Box>
  <Heading size="sm">Quick Actions</Heading>
  <ContextualAdminLinks elementName={selectedElement} />
</Box>
// Extend ContextualAdminLinks.tsx with new element types

const getLinksForElement = (element: string): AdminLink[] => {
  const links: Record<string, AdminLink[]> = {
    // ... existing links ...
    
    // Add your custom element
    'custom-gallery': [
      { 
        label: 'Manage Photos', 
        url: '/admin/custom-gallery', 
        icon: FiImage,
        description: 'Upload and organize photos'
      },
      { 
        label: 'Gallery Settings', 
        url: '/admin/settings/custom-gallery', 
        icon: FiSettings 
      },
    ],
  };
  
  return links[element] || [];
};

5. Full Integration Example

Complete Editable Section

import React, { useState } from 'react';
import { Box, VStack } from '@chakra-ui/react';
import InlineTextEditor from '@/components/editor/InlineTextEditor';
import ColumnLayoutManager from '@/components/editor/ColumnLayoutManager';
import CustomCSSEditor from '@/components/editor/CustomCSSEditor';
import ContextualAdminLinks from '@/components/editor/ContextualAdminLinks';

const EditableHeroSection: React.FC = () => {
  const [title, setTitle] = useState('<h1>Welcome</h1>');
  const [subtitle, setSubtitle] = useState('<p>Your club, your passion</p>');
  const [columns, setColumns] = useState([
    { id: '1', width: '60%', elements: [] },
    { id: '2', width: '40%', elements: [] }
  ]);
  const [customCSS, setCustomCSS] = useState('');
  
  const { isEditing } = useEditMode(); // Your edit mode hook

  return (
    <Box data-element="hero" position="relative">
      {/* Main Content */}
      <VStack spacing={4} align="stretch">
        {isEditing ? (
          <>
            <InlineTextEditor
              elementId="hero-title"
              initialContent={title}
              onSave={setTitle}
            />
            <InlineTextEditor
              elementId="hero-subtitle"
              initialContent={subtitle}
              onSave={setSubtitle}
            />
          </>
        ) : (
          <>
            <div dangerouslySetInnerHTML={{ __html: title }} />
            <div dangerouslySetInnerHTML={{ __html: subtitle }} />
          </>
        )}
      </VStack>
      
      {/* Editor Panel (shown when element is selected) */}
      {isEditing && (
        <Box
          position="fixed"
          right={4}
          top="100px"
          width="300px"
          bg="white"
          borderRadius="lg"
          boxShadow="xl"
          p={4}
        >
          <VStack align="stretch" spacing={4}>
            <ColumnLayoutManager
              elementName="hero"
              currentColumns={columns}
              onLayoutChange={setColumns}
            />
            
            <CustomCSSEditor
              elementName="hero"
              currentCSS={customCSS}
              onCSSChange={setCustomCSS}
            />
            
            <ContextualAdminLinks elementName="hero" />
          </VStack>
        </Box>
      )}
    </Box>
  );
};

export default EditableHeroSection;

6. Enhanced MyUIbrixEditor Integration

Adding New Components to Existing Editor

Update MyUIbrixEditor.tsx:

import InlineTextEditor from './InlineTextEditor';
import CustomCSSEditor from './CustomCSSEditor';
import ColumnLayoutManager from './ColumnLayoutManager';
import ContextualAdminLinks from './ContextualAdminLinks';

// Add state for new features
const [elementContent, setElementContent] = useState<Record<string, string>>({});
const [elementColumns, setElementColumns] = useState<Record<string, Column[]>>({});
const [elementCSS, setElementCSS] = useState<Record<string, string>>({});

// In the contextual style panel, add tabs
<Tabs>
  <TabList>
    <Tab>Style</Tab>
    <Tab>Layout</Tab>
    <Tab>CSS</Tab>
    <Tab>Content</Tab>
    <Tab>Admin</Tab>
  </TabList>
  
  <TabPanels>
    {/* Style Tab */}
    <TabPanel>
      <VisualStylePanel
        elementName={selectedElement}
        onStyleChange={handleStyleChange}
        currentStyles={elementStyles[selectedElement]}
      />
    </TabPanel>
    
    {/* Layout Tab */}
    <TabPanel>
      <ColumnLayoutManager
        elementName={selectedElement}
        currentColumns={elementColumns[selectedElement] || []}
        onLayoutChange={(cols) => {
          setElementColumns(prev => ({
            ...prev,
            [selectedElement]: cols
          }));
        }}
      />
    </TabPanel>
    
    {/* CSS Tab */}
    <TabPanel>
      <CustomCSSEditor
        elementName={selectedElement}
        currentCSS={elementCSS[selectedElement] || ''}
        onCSSChange={(css) => {
          setElementCSS(prev => ({
            ...prev,
            [selectedElement]: css
          }));
        }}
      />
    </TabPanel>
    
    {/* Content Tab */}
    <TabPanel>
      <VStack align="stretch" spacing={3}>
        <Text fontWeight="bold">Edit Content</Text>
        <Button 
          leftIcon={<FiEdit />}
          onClick={() => enableInlineEditingForElement(selectedElement)}
        >
          Enable Inline Editing
        </Button>
      </VStack>
    </TabPanel>
    
    {/* Admin Tab */}
    <TabPanel>
      <ContextualAdminLinks elementName={selectedElement} />
    </TabPanel>
  </TabPanels>
</Tabs>

7. Saving and Loading Data

Data Structure

interface ElementConfiguration {
  element_name: string;
  variant: string;
  visible: boolean;
  display_order: number;
  
  // New fields
  content?: Record<string, string>; // Inline edited content
  columns?: Column[]; // Column layout
  customCSS?: string; // Custom CSS
  customStyles?: Record<string, any>; // Style panel values
}

Save Function

const saveAllChanges = async () => {
  const configurations: ElementConfiguration[] = elementOrder.map((elementName, index) => ({
    page_type: pageType,
    element_name: elementName,
    variant: localChanges[elementName] || 'default',
    visible: visibleElements.has(elementName),
    display_order: index,
    
    // New data
    content: elementContent[elementName],
    columns: elementColumns[elementName],
    customCSS: elementCSS[elementName],
    customStyles: elementStyles[elementName],
  }));

  await batchUpdatePageElementConfigs(configurations);
  
  toast({
    title: 'All changes saved!',
    status: 'success',
    duration: 3000,
  });
};

Load Function

const loadConfigurations = async () => {
  const configs = await getPageElementConfigs(pageType);
  
  const content: Record<string, string> = {};
  const columns: Record<string, Column[]> = {};
  const css: Record<string, string> = {};
  const styles: Record<string, any> = {};
  
  configs.forEach(config => {
    if (config.content) content[config.element_name] = config.content;
    if (config.columns) columns[config.element_name] = config.columns;
    if (config.customCSS) css[config.element_name] = config.customCSS;
    if (config.customStyles) styles[config.element_name] = config.customStyles;
  });
  
  setElementContent(content);
  setElementColumns(columns);
  setElementCSS(css);
  setElementStyles(styles);
  
  // Apply CSS to DOM
  Object.entries(css).forEach(([elementName, cssString]) => {
    applyCustomCSS(elementName, cssString);
  });
};

8. Backend API Updates

Update API Endpoint

// In your page elements controller
type PageElementConfig struct {
    PageType      string                 `json:"page_type"`
    ElementName   string                 `json:"element_name"`
    Variant       string                 `json:"variant"`
    Visible       bool                   `json:"visible"`
    DisplayOrder  int                    `json:"display_order"`
    
    // New fields
    Content       map[string]string      `json:"content,omitempty"`
    Columns       []Column               `json:"columns,omitempty"`
    CustomCSS     string                 `json:"custom_css,omitempty"`
    CustomStyles  map[string]interface{} `json:"custom_styles,omitempty"`
}

type Column struct {
    ID       string   `json:"id"`
    Width    string   `json:"width"`
    Elements []string `json:"elements"`
}

Database Migration

-- Add new columns to page_elements table
ALTER TABLE page_elements
ADD COLUMN content JSONB,
ADD COLUMN columns JSONB,
ADD COLUMN custom_css TEXT,
ADD COLUMN custom_styles JSONB;

-- Create index for faster queries
CREATE INDEX idx_page_elements_custom_css ON page_elements(custom_css) WHERE custom_css IS NOT NULL;

9. Testing Checklist

  • Inline Editor

    • Click to edit activates editor
    • Formatting toolbar appears
    • Bold/Italic/Underline work
    • Links can be inserted
    • Auto-save on blur works
    • Changes persist after page reload
  • Column Layout

    • Templates apply correctly
    • Columns can be added/removed
    • Widths recalculate automatically
    • Layout persists after save
  • Custom CSS

    • Code editor works
    • Validation detects errors
    • Preview mode applies styles
    • Examples can be inserted
    • Styles persist after save
  • Admin Links

    • Links show for each element type
    • Links open in new tab
    • URLs are correct
    • Icons display properly
  • Integration

    • All components work together
    • No console errors
    • Performance is acceptable
    • Mobile responsive
    • Cross-browser compatible

10. Common Issues & Solutions

Issue: Inline editor not appearing

Solution: Ensure element has proper data attribute and is not nested incorrectly.

Issue: Custom CSS not applying

Solution: Check for syntax errors, ensure style tag is being created, check CSS specificity.

Issue: Column layout breaking

Solution: Verify total width is 100%, check for conflicting CSS, ensure grid is supported.

Solution: Verify routes exist, check authentication, ensure backend is running.


11. Performance Optimization

Lazy Loading

const InlineTextEditor = lazy(() => import('./InlineTextEditor'));
const CustomCSSEditor = lazy(() => import('./CustomCSSEditor'));
const ColumnLayoutManager = lazy(() => import('./ColumnLayoutManager'));

// Use with Suspense
<Suspense fallback={<Spinner />}>
  <InlineTextEditor {...props} />
</Suspense>

Debouncing Updates

import { debounce } from 'lodash';

const debouncedSave = debounce((content) => {
  saveToAPI(content);
}, 500);

<InlineTextEditor
  onSave={debouncedSave}
  {...otherProps}
/>

Memoization

const MemoizedColumnManager = React.memo(ColumnLayoutManager);
const MemoizedCSSEditor = React.memo(CustomCSSEditor);

12. Security Considerations

Sanitize User Input

import DOMPurify from 'dompurify';

const sanitizeHTML = (html: string) => {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'u', 'a', 'p', 'h1', 'h2', 'h3', 'span'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
  });
};

<InlineTextEditor
  onSave={(content) => {
    const clean = sanitizeHTML(content);
    saveToAPI(clean);
  }}
/>

Validate CSS

const isValidCSS = (css: string): boolean => {
  // Check for dangerous content
  if (css.includes('javascript:') || css.includes('<script')) {
    return false;
  }
  
  // Check for balanced braces
  const openBraces = (css.match(/{/g) || []).length;
  const closeBraces = (css.match(/}/g) || []).length;
  
  return openBraces === closeBraces;
};

Summary

All new Elementor-style components are now integrated into MyUIbrix:

Inline Text Editor - Rich text editing in place
Column Layout Manager - Visual layout builder
Custom CSS Editor - Full CSS control
Contextual Admin Links - Smart navigation
Enhanced Style Panel - Complete styling tools

The system is modular, type-safe, and production-ready!

Next Steps:

  1. Test all features thoroughly
  2. Deploy to staging environment
  3. Train users on new features
  4. Monitor performance and feedback
  5. Iterate based on user needs