🚀 Korea Tourism Agency - Complete implementation
✨ Features: - Modern tourism website with responsive design - AdminJS admin panel with image editor integration - PostgreSQL database with comprehensive schema - Docker containerization - Image upload and gallery management 🛠 Tech Stack: - Backend: Node.js + Express.js - Database: PostgreSQL 13+ - Frontend: HTML/CSS/JS with responsive design - Admin: AdminJS with custom components - Deployment: Docker + Docker Compose - Image Processing: Sharp with optimization 📱 Admin Features: - Routes/Tours management (city, mountain, fishing) - Guides profiles with specializations - Articles and blog system - Image editor with upload/gallery/URL options - User management and authentication - Responsive admin interface 🎨 Design: - Korean tourism focused branding - Mobile-first responsive design - Custom CSS with modern aesthetics - Image optimization and gallery - SEO-friendly structure 🔒 Security: - Helmet.js security headers - bcrypt password hashing - Input validation and sanitization - CORS protection - Environment variables
This commit is contained in:
130
src/components/ImageSelector.jsx
Normal file
130
src/components/ImageSelector.jsx
Normal file
@@ -0,0 +1,130 @@
|
||||
// Простой компонент для выбора изображений, который работает с AdminJS
|
||||
import React from 'react';
|
||||
|
||||
const ImageSelector = ({ record, property, onChange }) => {
|
||||
const currentValue = record.params[property.name] || '';
|
||||
|
||||
const openImagePicker = () => {
|
||||
// Создаем модальное окно с iframe для редактора изображений
|
||||
const modal = document.createElement('div');
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.style.cssText = `
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = '✕';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #ff4757;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.src = `/image-editor.html?field=${property.name}¤t=${encodeURIComponent(currentValue)}`;
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
`;
|
||||
|
||||
closeBtn.onclick = () => document.body.removeChild(modal);
|
||||
|
||||
// Слушаем сообщения от iframe
|
||||
const handleMessage = (event) => {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
|
||||
if (event.data.type === 'imageSelected' && event.data.targetField === property.name) {
|
||||
onChange(property.name, event.data.path);
|
||||
document.body.removeChild(modal);
|
||||
window.removeEventListener('message', handleMessage);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
|
||||
content.appendChild(closeBtn);
|
||||
content.appendChild(iframe);
|
||||
modal.appendChild(content);
|
||||
document.body.appendChild(modal);
|
||||
};
|
||||
|
||||
return React.createElement('div', null,
|
||||
React.createElement('label', {
|
||||
style: { fontWeight: 'bold', marginBottom: '8px', display: 'block' }
|
||||
}, property.label || property.name),
|
||||
|
||||
React.createElement('div', {
|
||||
style: { display: 'flex', alignItems: 'center', marginBottom: '12px' }
|
||||
},
|
||||
React.createElement('input', {
|
||||
type: 'text',
|
||||
value: currentValue,
|
||||
onChange: (e) => onChange(property.name, e.target.value),
|
||||
placeholder: 'Путь к изображению',
|
||||
style: {
|
||||
flex: 1,
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
marginRight: '8px'
|
||||
}
|
||||
}),
|
||||
React.createElement('button', {
|
||||
type: 'button',
|
||||
onClick: openImagePicker,
|
||||
style: {
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}, 'Выбрать')
|
||||
),
|
||||
|
||||
currentValue && React.createElement('img', {
|
||||
src: currentValue,
|
||||
alt: 'Preview',
|
||||
style: {
|
||||
maxWidth: '200px',
|
||||
maxHeight: '200px',
|
||||
objectFit: 'cover',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
onError: (e) => {
|
||||
e.target.style.display = 'none';
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageSelector;
|
||||
Reference in New Issue
Block a user