feat: Add Dockerfile and initial Docker setup files

This commit is contained in:
komkida91
2026-01-31 16:04:55 +01:00
parent 70c533e755
commit 59812e547e
31 changed files with 7720 additions and 1776 deletions

188
demo/customizer.html Normal file
View File

@@ -0,0 +1,188 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>GKACHELE Customizer</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
body { font-family: system-ui, Arial; margin:0; background:#f0f0f1; }
.wrap { display:flex; height:100vh; }
.sidebar { width:340px; background:#fff; border-right:1px solid #ddd; padding:12px; overflow:auto }
.preview { flex:1; display:flex; flex-direction:column }
.preview-header { padding:12px; background:#fff; border-bottom:1px solid #ddd }
.preview-body { padding:20px; overflow:auto; }
.btn { padding:8px 12px; border-radius:4px; border:0; cursor:pointer }
.btn-primary { background:#2271b1; color:#fff }
.btn-secondary { background:#f0f0f1 }
.block-item { padding:10px; border:1px solid #e5e5e5; margin-bottom:8px; border-radius:4px; display:flex; justify-content:space-between }
</style>
</head>
<body>
<div class="wrap">
<div class="sidebar">
<h3>GKACHELE™ Customizer</h3>
<div>
<h4>Ajustes</h4>
<label>Nombre sitio<br><input id="siteName" placeholder="Mi Sitio"></label>
<label>Color primario<br><input type="color" id="colorPrimary" value="#2271b1"></label>
</div>
<hr>
<div>
<h4>Añadir Bloque</h4>
<button class="btn btn-primary" onclick="addBlock('heading')">Encabezado</button>
<button class="btn btn-primary" onclick="addBlock('paragraph')">Párrafo</button>
<button class="btn btn-primary" onclick="addBlock('image')">Imagen</button>
</div>
<hr>
<div>
<h4>Bloques</h4>
<div id="blocksList"></div>
</div>
</div>
<div class="preview">
<div class="preview-header">
<span id="previewTitle">Vista previa</span>
<div style="float:right">
<button class="btn btn-secondary" onclick="discardChanges()">Descartar</button>
<button class="btn btn-primary" onclick="saveChanges()">Guardar y Publicar</button>
</div>
</div>
<div class="preview-body">
<div id="previewBlocks"></div>
</div>
</div>
</div>
<script>
// Simple customizer adapted to GKACHELE backend
let state = {
siteId: (new URLSearchParams(window.location.search)).get('site_id') || null,
blocks: [],
settings: { siteName:'Mi Sitio', colorPrimary:'#2271b1' },
hasChanges: false,
};
document.addEventListener('DOMContentLoaded', async () => {
// populate from backend if editing a real site
await loadFromBackendIfAvailable();
// fallback: localStorage
loadFromLocalStorage();
renderUI();
});
function loadFromLocalStorage(){
if (!state.siteId) {
const saved = localStorage.getItem('gkachele_customizer_demo');
if (saved) {
const d = JSON.parse(saved);
state.blocks = d.blocks||state.blocks;
state.settings = {...state.settings, ...(d.settings||{})};
}
}
}
async function loadFromBackendIfAvailable(){
if (!state.siteId) return;
try{
const r = await fetch(`/api/customizer/get-content/${state.siteId}`);
const j = await r.json();
if (j && j.success) {
const c = j.content || {};
state.blocks = c.blocks || [];
state.settings = {...state.settings, ...(c.settings||{})};
state.hasChanges = false;
updateSettingsUI();
}
}catch(e){ console.warn('Backend load failed', e); }
}
function updateSettingsUI(){
document.getElementById('siteName').value = state.settings.siteName||'';
document.getElementById('colorPrimary').value = state.settings.colorPrimary||'#2271b1';
}
function renderUI(){
updateSettingsUI();
renderBlocksList();
renderPreview();
}
function addBlock(type){
const b = { id:'b_'+Date.now(), type:type, data: getDefault(type) };
state.blocks.push(b);
state.hasChanges = true;
renderUI();
}
function getDefault(type){
if (type==='heading') return {text:'Título'};
if (type==='paragraph') return {text:'Párrafo de ejemplo'};
if (type==='image') return {url:'https://via.placeholder.com/600x300'};
return {};
}
function renderBlocksList(){
const el = document.getElementById('blocksList');
if (!state.blocks.length) { el.innerHTML='<div style="color:#888">No hay bloques</div>'; return; }
el.innerHTML = state.blocks.map((b,i)=>`<div class="block-item"><div>${b.type}</div><div><button onclick="editBlock(${i})">✏️</button> <button onclick="deleteBlock(${i})">🗑️</button></div></div>`).join('');
}
function renderPreview(){
const el = document.getElementById('previewBlocks');
if (!state.blocks.length) { el.innerHTML='<div style="color:#999">Añade bloques para ver la preview</div>'; return; }
el.innerHTML = state.blocks.map(b=>renderBlockHtml(b)).join('');
document.getElementById('previewTitle').textContent = state.settings.siteName || 'Vista previa';
}
function renderBlockHtml(b){
if (b.type==='heading') return `<h2>${escapeHtml(b.data.text)}</h2>`;
if (b.type==='paragraph') return `<p>${escapeHtml(b.data.text)}</p>`;
if (b.type==='image') return `<img src="${escapeHtml(b.data.url)}" style="max-width:100%;height:auto"/>`;
return `<div>${b.type}</div>`;
}
function editBlock(idx){
const b = state.blocks[idx];
const value = prompt('Editar contenido', b.type==='image'?b.data.url:b.data.text);
if (value!==null){
if (b.type==='image') b.data.url = value; else b.data.text = value;
state.hasChanges = true; renderUI();
}
}
function deleteBlock(idx){ if (confirm('Eliminar bloque?')) { state.blocks.splice(idx,1); state.hasChanges=true; renderUI(); } }
function escapeHtml(s){ if (!s) return ''; return s.replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;'); }
function discardChanges(){ if (!state.hasChanges){ alert('No hay cambios'); return;} if (confirm('Descartar cambios?')){ state.blocks=[]; state.settings={siteName:'Mi Sitio',colorPrimary:'#2271b1'}; localStorage.removeItem('gkachele_customizer_demo'); renderUI(); state.hasChanges=false;} }
function updateSaveIndicator(status){ console.log('save:',status); }
function saveChanges(){
// collect settings from UI
state.settings.siteName = document.getElementById('siteName').value;
state.settings.colorPrimary = document.getElementById('colorPrimary').value;
const content = { blocks: state.blocks, settings: state.settings };
// save local demo
if (!state.siteId) localStorage.setItem('gkachele_customizer_demo', JSON.stringify(content));
// send to backend using API expected by server: { site_id, content }
if (!state.siteId) { alert('Guardado local (demo). Para guardar en SaaS abre el customizer con ?site_id=ID'); state.hasChanges=false; return; }
updateSaveIndicator('saving');
fetch('/api/customizer/save', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ site_id: state.siteId, content: content })
}).then(r=>r.json()).then(j=>{
if (j && j.success) { state.hasChanges=false; updateSaveIndicator('saved'); alert('Guardado en servidor'); }
else { updateSaveIndicator('error'); alert('Error guardando'); }
}).catch(e=>{ updateSaveIndicator('error'); alert('Error guardando: '+e); });
}
</script>
</body>
</html>