mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #62
This commit is contained in:
@@ -0,0 +1,687 @@
|
||||
# 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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
// 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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Contextual Admin Links Integration
|
||||
|
||||
### Basic Usage
|
||||
```tsx
|
||||
import ContextualAdminLinks from '@/components/editor/ContextualAdminLinks';
|
||||
|
||||
// In your style panel or settings popup
|
||||
<Box>
|
||||
<Heading size="sm">Quick Actions</Heading>
|
||||
<ContextualAdminLinks elementName={selectedElement} />
|
||||
</Box>
|
||||
```
|
||||
|
||||
### Custom Links for New Elements
|
||||
```tsx
|
||||
// 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
|
||||
```tsx
|
||||
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`:
|
||||
|
||||
```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
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
```go
|
||||
// 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
|
||||
```sql
|
||||
-- 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.
|
||||
|
||||
### Issue: Admin links not working
|
||||
**Solution**: Verify routes exist, check authentication, ensure backend is running.
|
||||
|
||||
---
|
||||
|
||||
## 11. Performance Optimization
|
||||
|
||||
### Lazy Loading
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const debouncedSave = debounce((content) => {
|
||||
saveToAPI(content);
|
||||
}, 500);
|
||||
|
||||
<InlineTextEditor
|
||||
onSave={debouncedSave}
|
||||
{...otherProps}
|
||||
/>
|
||||
```
|
||||
|
||||
### Memoization
|
||||
```tsx
|
||||
const MemoizedColumnManager = React.memo(ColumnLayoutManager);
|
||||
const MemoizedCSSEditor = React.memo(CustomCSSEditor);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Security Considerations
|
||||
|
||||
### Sanitize User Input
|
||||
```tsx
|
||||
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
|
||||
```tsx
|
||||
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
|
||||
Reference in New Issue
Block a user