Files
app_compartir/web/templates/index.html
T

338 lines
17 KiB
HTML

{{template "base.html" .}}
{{define "title"}}Compartir — Inicio{{end}}
{{define "content"}}
<div class="max-w-3xl mx-auto space-y-8">
<!-- Hero / Header -->
<div class="text-center space-y-2">
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight text-white">
Pasa archivos y texto <span class="text-brand-500">al instante</span>
</h1>
<p class="text-slate-400 text-sm sm:text-base max-w-xl mx-auto">
Olvídate de abrir WhatsApp para pasarte fotos, PDFs o notas entre tu PC y tu teléfono. Sube y escanea el QR o copia el enlace.
</p>
</div>
<!-- Mode Selector Tabs -->
<div class="flex items-center justify-center p-1 bg-slate-900 border border-slate-800 rounded-2xl max-w-md mx-auto">
<button id="tabFilesBtn" onclick="switchTab('files')" class="flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 bg-slate-800 text-white shadow">
<i class="fa-solid fa-cloud-arrow-up text-brand-500"></i>
<span>Archivos / Fotos</span>
</button>
<button id="tabTextBtn" onclick="switchTab('text')" class="flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 text-slate-400 hover:text-white">
<i class="fa-solid fa-code text-indigo-400"></i>
<span>Texto / Código</span>
</button>
</div>
<!-- Tab 1: File Upload -->
<div id="tabFiles" class="space-y-4">
<div id="dropZone" class="relative group border-2 border-dashed border-slate-700 hover:border-brand-500/80 bg-slate-900/50 hover:bg-slate-900/90 rounded-3xl p-8 sm:p-12 text-center transition-all duration-200 cursor-pointer">
<input type="file" id="fileInput" class="hidden" multiple>
<div class="space-y-4">
<div class="w-16 h-16 mx-auto rounded-2xl bg-brand-500/10 border border-brand-500/20 text-brand-400 flex items-center justify-center text-2xl group-hover:scale-110 transition duration-200">
<i class="fa-solid fa-arrow-up-from-bracket"></i>
</div>
<div class="space-y-1">
<p class="text-base sm:text-lg font-semibold text-white">
Arrastra tus archivos aquí o <span class="text-brand-400 underline decoration-brand-500/30">haz clic para explorar</span>
</p>
<p class="text-xs text-slate-400">
Imágenes, videos, audios, PDFs, ZIPs o cualquier archivo. También puedes <kbd class="px-2 py-0.5 rounded bg-slate-800 border border-slate-700 text-slate-300 font-mono text-[11px]">Ctrl+V</kbd> para pegar capturas directamente.
</p>
</div>
<div class="flex items-center justify-center gap-4 pt-2 text-xs text-slate-500">
<span class="flex items-center gap-1.5"><i class="fa-solid fa-shield text-emerald-400"></i> Encriptación TLS</span>
{{if .Session.IsGuest}}
<span class="flex items-center gap-1.5"><i class="fa-regular fa-clock text-amber-400"></i> Borrado automático en 24h</span>
{{else}}
<span class="flex items-center gap-1.5"><i class="fa-solid fa-database text-emerald-400"></i> Guardado permanente</span>
{{end}}
</div>
</div>
<!-- Uploading overlay / progress -->
<div id="uploadProgress" class="hidden absolute inset-0 bg-slate-950/90 backdrop-blur-sm rounded-3xl flex flex-col items-center justify-center p-6 space-y-4">
<div class="w-12 h-12 border-4 border-slate-700 border-t-brand-500 rounded-full animate-spin"></div>
<div class="text-center space-y-1">
<p id="progressText" class="text-sm font-semibold text-white">Subiendo archivo...</p>
<div class="w-64 bg-slate-800 rounded-full h-2 overflow-hidden">
<div id="progressBar" class="bg-brand-500 h-2 rounded-full w-0 transition-all duration-150"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Tab 2: Text / Code Share -->
<div id="tabText" class="hidden space-y-4">
<form id="textForm" onsubmit="handleShareText(event)" class="bg-slate-900 border border-slate-800 rounded-3xl p-6 space-y-4 shadow-xl">
<div class="space-y-1">
<label for="textTitle" class="block text-xs font-semibold text-slate-300">Título o descripción (opcional)</label>
<input type="text" id="textTitle" placeholder="Ej: Notas de reunión, comando curl, contraseña temporal..." class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-sm text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition">
</div>
<div class="space-y-1">
<label for="textContent" class="block text-xs font-semibold text-slate-300">Contenido (Texto, Código, Enlaces, Markdown)</label>
<textarea id="textContent" rows="8" required placeholder="Escribe o pega aquí tu texto o código para compartirlo..." class="w-full bg-slate-950 border border-slate-800 rounded-xl p-4 text-sm font-mono text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition custom-scrollbar"></textarea>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-xs text-slate-500">
{{if .Session.IsGuest}}⏰ Caducará a las 24 horas{{else}}🔒 Se guardará de forma permanente{{end}}
</span>
<button type="submit" id="submitTextBtn" class="px-6 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-sm transition flex items-center gap-2 shadow-lg shadow-indigo-600/20">
<i class="fa-solid fa-paper-plane"></i>
<span>Compartir Texto</span>
</button>
</div>
</form>
</div>
<!-- Success Result Card (Hidden initially) -->
<div id="resultCard" class="hidden bg-slate-900 border border-slate-800 rounded-3xl p-6 sm:p-8 space-y-6 shadow-2xl animate-fade-in">
<div class="flex items-center justify-between border-b border-slate-800 pb-4">
<div class="flex items-center gap-3">
<span class="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center text-lg">
<i class="fa-solid fa-check"></i>
</span>
<div>
<h3 id="resultTitle" class="text-base font-bold text-white">¡Listo para compartir!</h3>
<p id="resultSubtitle" class="text-xs text-slate-400">Escanea el QR o copia el enlace</p>
</div>
</div>
<button onclick="resetUpload()" class="text-xs text-slate-400 hover:text-white px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 transition">
<i class="fa-solid fa-plus mr-1"></i> Subir otro
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-center">
<!-- QR Code Section -->
<div class="bg-slate-950 p-6 rounded-2xl border border-slate-800 flex flex-col items-center justify-center space-y-3">
<div class="p-2 bg-white rounded-xl shadow-lg">
<img id="qrImage" src="" alt="Código QR para escanear" class="w-44 h-44 object-contain">
</div>
<p class="text-xs text-slate-400 text-center flex items-center gap-1.5">
<i class="fa-solid fa-mobile-screen text-brand-400"></i>
<span>Apunta con la cámara de tu móvil para abrir</span>
</p>
</div>
<!-- Link & Actions Section -->
<div class="space-y-4">
<div class="space-y-1.5">
<label class="block text-xs font-semibold text-slate-400">Enlace directo para compartir</label>
<div class="flex items-center gap-2">
<input type="text" id="shareUrlInput" readonly class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2.5 text-sm font-mono text-slate-200 select-all focus:outline-none">
<button onclick="copyShareUrl()" id="copyBtn" class="px-4 py-2.5 rounded-xl bg-brand-600 hover:bg-brand-500 text-slate-950 font-bold text-sm transition flex items-center gap-1.5 shrink-0 shadow-lg shadow-brand-500/20">
<i class="fa-regular fa-copy"></i>
<span id="copyBtnText">Copiar</span>
</button>
</div>
</div>
<div class="flex gap-2">
<a id="openViewerBtn" href="#" target="_blank" class="flex-1 py-2.5 px-4 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-white font-semibold text-sm transition text-center flex items-center justify-center gap-2">
<i class="fa-solid fa-eye text-brand-400"></i>
<span>Abrir Visor</span>
</a>
<a id="directDownloadBtn" href="#" class="py-2.5 px-4 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-300 font-semibold text-sm transition flex items-center justify-center gap-2" title="Descarga directa">
<i class="fa-solid fa-download"></i>
</a>
</div>
<!-- Expiration notice -->
<div id="retentionNotice" class="p-3 rounded-xl text-xs flex items-center gap-2">
<!-- Dynamic notice text inserted via JS -->
</div>
</div>
</div>
</div>
</div>
{{end}}
{{define "scripts"}}
<script>
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const uploadProgress = document.getElementById('uploadProgress');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const resultCard = document.getElementById('resultCard');
function switchTab(tab) {
if (tab === 'files') {
document.getElementById('tabFiles').classList.remove('hidden');
document.getElementById('tabText').classList.add('hidden');
document.getElementById('tabFilesBtn').className = "flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 bg-slate-800 text-white shadow";
document.getElementById('tabTextBtn').className = "flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 text-slate-400 hover:text-white";
} else {
document.getElementById('tabFiles').classList.add('hidden');
document.getElementById('tabText').classList.remove('hidden');
document.getElementById('tabTextBtn').className = "flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 bg-slate-800 text-white shadow";
document.getElementById('tabFilesBtn').className = "flex-1 py-2 px-4 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2 text-slate-400 hover:text-white";
}
}
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-brand-500', 'bg-slate-900/90');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('border-brand-500', 'bg-slate-900/90');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-brand-500', 'bg-slate-900/90');
if (e.dataTransfer.files.length > 0) {
uploadFile(e.dataTransfer.files[0]);
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) {
uploadFile(fileInput.files[0]);
}
});
// Handle Ctrl+V paste anywhere on window
window.addEventListener('paste', (e) => {
// If active element is a textarea or text input, let default paste happen
if (document.activeElement.tagName === 'TEXTAREA' || document.activeElement.tagName === 'INPUT') {
return;
}
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
for (let item of items) {
if (item.kind === 'file') {
const file = item.getAsFile();
if (file) {
uploadFile(file);
break;
}
} else if (item.kind === 'string' && item.type === 'text/plain') {
item.getAsString((text) => {
switchTab('text');
document.getElementById('textContent').value = text;
});
}
}
});
function uploadFile(file) {
uploadProgress.classList.remove('hidden');
progressBar.style.width = '0%';
progressText.innerText = `Subiendo ${file.name}...`;
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percent + '%';
progressText.innerText = `Subiendo ${file.name} (${percent}%)...`;
}
};
xhr.onload = () => {
uploadProgress.classList.add('hidden');
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
showResult(res);
} else {
alert('Error al subir el archivo: ' + xhr.responseText);
}
};
xhr.onerror = () => {
uploadProgress.classList.add('hidden');
alert('Error de red al subir el archivo.');
};
xhr.send(formData);
}
async function handleShareText(e) {
e.preventDefault();
const title = document.getElementById('textTitle').value;
const text = document.getElementById('textContent').value;
const btn = document.getElementById('submitTextBtn');
btn.disabled = true;
btn.innerHTML = `<i class="fa-solid fa-circle-notch animate-spin"></i> Compartiendo...`;
try {
const resp = await fetch('/api/share-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, text })
});
const data = await resp.json();
if (resp.ok) {
showResult(data);
} else {
alert('Error al compartir texto: ' + (data.error || 'Desconocido'));
}
} catch (err) {
alert('Error de red: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = `<i class="fa-solid fa-paper-plane"></i> <span>Compartir Texto</span>`;
}
}
function showResult(data) {
document.getElementById('tabFiles').classList.add('hidden');
document.getElementById('tabText').classList.add('hidden');
resultCard.classList.remove('hidden');
document.getElementById('resultTitle').innerText = data.title || 'Contenido compartido';
document.getElementById('shareUrlInput').value = data.viewer_url;
document.getElementById('qrImage').src = `/qr/${data.id}`;
document.getElementById('openViewerBtn').href = data.viewer_url;
document.getElementById('directDownloadBtn').href = data.download_url;
const notice = document.getElementById('retentionNotice');
if (data.is_guest) {
notice.className = "p-3 rounded-xl text-xs flex items-center gap-2 bg-amber-500/10 border border-amber-500/20 text-amber-300";
notice.innerHTML = `<i class="fa-regular fa-clock text-amber-400"></i> <span><strong>Sesión Invitado:</strong> Este contenido se eliminará automáticamente en 24 horas (${data.expires_in || '24h'}).</span>`;
} else {
notice.className = "p-3 rounded-xl text-xs flex items-center gap-2 bg-emerald-500/10 border border-emerald-500/20 text-emerald-300";
notice.innerHTML = `<i class="fa-solid fa-shield text-emerald-400"></i> <span><strong>Cuenta registrada:</strong> Almacenamiento permanente. Puedes gestionarlo desde Mis Archivos.</span>`;
}
resultCard.scrollIntoView({ behavior: 'smooth' });
}
function resetUpload() {
resultCard.classList.add('hidden');
fileInput.value = '';
document.getElementById('textContent').value = '';
document.getElementById('textTitle').value = '';
switchTab('files');
}
function copyShareUrl() {
const input = document.getElementById('shareUrlInput');
input.select();
navigator.clipboard.writeText(input.value).then(() => {
const btnText = document.getElementById('copyBtnText');
btnText.innerText = '¡Copiado!';
setTimeout(() => {
btnText.innerText = 'Copiar';
}, 2500);
});
}
</script>
{{end}}