Spaces:
Running
Running
| <html lang="vi"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Professional Temp Mail - Full Version</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { font-family: 'Inter', sans-serif; background-color: #0f172a; color: #f1f5f9; } | |
| .dark-card { background: #1e293b; border: 1px solid #334155; } | |
| .mail-content-wrapper { background: white; border-radius: 8px; overflow: hidden; min-height: 400px; } | |
| .mail-content-wrapper iframe { width: 100%; border: none; height: 100%; min-height: 400px; } | |
| .loader { border-top-color: #6366f1; animation: spinner 0.6s linear infinite; } | |
| @keyframes spinner { to { transform: rotate(360deg); } } | |
| .animate-fade-in { animation: fadeIn 0.3s ease-out forwards; } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } | |
| /* Attachment styling */ | |
| .attachment-chip { | |
| background: #f1f5f9; | |
| color: #334155; | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| border: 1px solid #e2e8f0; | |
| transition: all 0.2s; | |
| } | |
| .attachment-chip:hover { | |
| background: #e2e8f0; | |
| color: #4f46e5; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen py-10 px-4"> | |
| <div class="max-w-4xl mx-auto"> | |
| <!-- Header --> | |
| <div class="mb-8"> | |
| <h1 class="text-3xl font-bold text-white flex items-center gap-3"> | |
| <i class="fa-solid fa-shield-halved text-indigo-500"></i> Temp Mail Pro | |
| </h1> | |
| <p class="text-slate-400 mt-1">Dịch vụ email tạm thời - Hỗ trợ tệp đính kèm</p> | |
| </div> | |
| <!-- Mail Creation Area --> | |
| <div class="dark-card rounded-2xl p-6 shadow-2xl mb-6"> | |
| <label class="block text-sm font-medium text-slate-400 mb-2 uppercase tracking-wider">Địa chỉ email hiện tại</label> | |
| <div class="flex flex-col sm:flex-row gap-3"> | |
| <div class="relative flex-grow"> | |
| <input type="text" id="emailAddr" readonly placeholder="Đang khởi tạo..." class="w-full bg-slate-900 border border-slate-700 rounded-xl py-3 px-4 pr-12 font-mono text-indigo-400 focus:outline-none focus:border-indigo-500"> | |
| <button onclick="copyEmail()" class="absolute right-3 top-3 text-slate-500 hover:text-indigo-400 transition-colors"> | |
| <i class="fa-regular fa-copy text-xl"></i> | |
| </button> | |
| </div> | |
| <button onclick="createNewAccount()" class="bg-slate-700 hover:bg-slate-600 text-white font-semibold py-3 px-6 rounded-xl flex items-center justify-center gap-2 transition-all active:scale-95"> | |
| <i class="fa-solid fa-rotate"></i> Đổi địa chỉ | |
| </button> | |
| </div> | |
| <div id="copyToast" class="hidden text-xs text-emerald-400 mt-2 font-medium text-center">Đã sao chép địa chỉ vào clipboard</div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="flex items-center justify-between mb-4 px-2"> | |
| <div class="flex items-center gap-3"> | |
| <h2 class="text-xl font-bold text-slate-200">Hộp thư đến</h2> | |
| <div id="refreshLoader" class="hidden w-5 h-5 border-2 border-slate-600 loader rounded-full"></div> | |
| </div> | |
| <button onclick="checkInbox()" class="bg-indigo-600/10 hover:bg-indigo-600/20 text-indigo-400 text-sm font-semibold py-2 px-4 rounded-lg flex items-center gap-2 transition-all border border-indigo-500/20 active:scale-95"> | |
| <i class="fa-solid fa-arrows-rotate" id="refreshIcon"></i> Làm mới hộp thư | |
| </button> | |
| </div> | |
| <!-- Inbox List --> | |
| <div id="inboxContainer" class="space-y-3"> | |
| <div id="emptyState" class="text-center py-20 dark-card rounded-2xl border-dashed border-2 border-slate-700"> | |
| <i class="fa-solid fa-inbox text-5xl text-slate-700 mb-4"></i> | |
| <p class="text-slate-500">Chưa có thư. Nhấn "Làm mới" để kiểm tra.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Modal Xem Thư --> | |
| <div id="mailModal" class="fixed inset-0 bg-slate-950/80 hidden z-50 flex items-center justify-center p-4"> | |
| <div class="dark-card w-full max-w-4xl rounded-2xl shadow-2xl flex flex-col max-h-[90vh]"> | |
| <div class="p-4 border-b border-slate-700 flex justify-between items-center bg-slate-800 rounded-t-2xl"> | |
| <h3 class="font-bold text-slate-200 truncate pr-4" id="modalSubject">Tiêu đề thư</h3> | |
| <button onclick="closeMail()" class="text-slate-500 hover:text-white text-2xl px-2">×</button> | |
| </div> | |
| <!-- Mail Header Info --> | |
| <div class="p-4 bg-slate-900 border-b border-slate-700 text-sm"> | |
| <div id="modalFrom" class="text-indigo-400 font-medium"></div> | |
| <div id="modalTime" class="text-slate-500 text-xs mt-1"></div> | |
| <!-- Attachments Area --> | |
| <div id="attachmentArea" class="hidden mt-4 pt-3 border-t border-slate-800"> | |
| <p class="text-[10px] text-slate-500 uppercase tracking-widest mb-2">Tệp đính kèm:</p> | |
| <div id="attachmentList" class="flex flex-wrap gap-2"></div> | |
| </div> | |
| </div> | |
| <div class="p-4 overflow-auto flex-grow bg-slate-100 rounded-b-2xl"> | |
| <div id="modalBody" class="mail-content-wrapper"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API = "https://api.mail.tm"; | |
| let currentToken = ""; | |
| let currentEmail = ""; | |
| async function init() { | |
| await createNewAccount(); | |
| } | |
| async function createNewAccount() { | |
| try { | |
| const domains = await fetch(`${API}/domains`).then(r => r.json()); | |
| const domain = domains['hydra:member'][0].domain; | |
| const user = Math.random().toString(36).substring(2, 12); | |
| const address = `${user}@${domain}`; | |
| const password = "password123"; | |
| await fetch(`${API}/accounts`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({address, password}) | |
| }); | |
| const tokenData = await fetch(`${API}/token`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({address, password}) | |
| }).then(r => r.json()); | |
| currentToken = tokenData.token; | |
| currentEmail = address; | |
| document.getElementById('emailAddr').value = address; | |
| document.getElementById('inboxContainer').innerHTML = ` | |
| <div id="emptyState" class="text-center py-20 dark-card rounded-2xl border-dashed border-2 border-slate-700"> | |
| <i class="fa-solid fa-inbox text-5xl text-slate-700 mb-4"></i> | |
| <p class="text-slate-500">Đang chờ thư mới...</p> | |
| </div>`; | |
| checkInbox(); | |
| } catch (err) { console.error(err); } | |
| } | |
| async function checkInbox() { | |
| if (!currentToken) return; | |
| const loader = document.getElementById('refreshLoader'); | |
| const icon = document.getElementById('refreshIcon'); | |
| loader.classList.remove('hidden'); | |
| if (icon) icon.classList.add('fa-spin'); | |
| try { | |
| const res = await fetch(`${API}/messages`, { | |
| headers: {'Authorization': `Bearer ${currentToken}`} | |
| }).then(r => r.json()); | |
| renderInbox(res['hydra:member']); | |
| } catch (err) { console.error(err); } finally { | |
| setTimeout(() => { | |
| loader.classList.add('hidden'); | |
| if (icon) icon.classList.remove('fa-spin'); | |
| }, 500); | |
| } | |
| } | |
| function renderInbox(msgs) { | |
| const container = document.getElementById('inboxContainer'); | |
| const empty = document.getElementById('emptyState'); | |
| if (!msgs || msgs.length === 0) return; | |
| if (empty) empty.remove(); | |
| const currentIds = Array.from(container.querySelectorAll('.mail-item')).map(m => m.dataset.id); | |
| msgs.forEach(m => { | |
| if (!currentIds.includes(m.id)) { | |
| const div = document.createElement('div'); | |
| div.className = "mail-item bg-slate-800 p-4 rounded-xl border border-slate-700 hover:border-indigo-500 transition-all cursor-pointer flex justify-between items-center group shadow-sm animate-fade-in"; | |
| div.dataset.id = m.id; | |
| div.onclick = () => openMail(m.id); | |
| // Hiển thị icon nếu có file đính kèm | |
| const hasAttach = m.hasAttachments ? '<i class="fa-solid fa-paperclip text-slate-500 mr-2"></i>' : ''; | |
| div.innerHTML = ` | |
| <div class="flex-grow mr-4 overflow-hidden"> | |
| <div class="flex items-center gap-2 mb-1"> | |
| <span class="text-xs font-bold text-indigo-400 uppercase tracking-tighter truncate">${m.from.address}</span> | |
| <span class="text-[10px] text-slate-500 whitespace-nowrap">${new Date(m.createdAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span> | |
| </div> | |
| <div class="font-semibold text-slate-200 truncate">${hasAttach}${m.subject || '(Không có tiêu đề)'}</div> | |
| </div> | |
| <i class="fa-solid fa-arrow-right-long text-slate-600 group-hover:text-indigo-400 group-hover:translate-x-1 transition-all"></i> | |
| `; | |
| container.prepend(div); | |
| } | |
| }); | |
| } | |
| async function openMail(id) { | |
| const modal = document.getElementById('mailModal'); | |
| const bodyWrapper = document.getElementById('modalBody'); | |
| const attachmentArea = document.getElementById('attachmentArea'); | |
| const attachmentList = document.getElementById('attachmentList'); | |
| bodyWrapper.innerHTML = '<div class="flex justify-center p-20"><div class="loader w-10 h-10 border-4 rounded-full"></div></div>'; | |
| attachmentArea.classList.add('hidden'); | |
| attachmentList.innerHTML = ''; | |
| modal.classList.remove('hidden'); | |
| try { | |
| const m = await fetch(`${API}/messages/${id}`, { | |
| headers: {'Authorization': `Bearer ${currentToken}`} | |
| }).then(r => r.json()); | |
| document.getElementById('modalSubject').innerText = m.subject || 'No Subject'; | |
| document.getElementById('modalFrom').innerText = `Từ: ${m.from.address}`; | |
| document.getElementById('modalTime').innerText = new Date(m.createdAt).toLocaleString(); | |
| // Xử lý tệp đính kèm | |
| if (m.attachments && m.attachments.length > 0) { | |
| attachmentArea.classList.remove('hidden'); | |
| m.attachments.forEach(file => { | |
| const downloadUrl = `${API}${file.downloadUrl}`; | |
| const fileSize = (file.size / 1024).toFixed(1) + ' KB'; | |
| const chip = document.createElement('a'); | |
| chip.href = "javascript:void(0)"; | |
| chip.onclick = (e) => downloadFile(downloadUrl, file.filename); | |
| chip.className = "attachment-chip"; | |
| chip.innerHTML = ` | |
| <i class="fa-solid fa-file-arrow-down"></i> | |
| <span>${file.filename} <span class="text-slate-400">(${fileSize})</span></span> | |
| `; | |
| attachmentList.appendChild(chip); | |
| }); | |
| } | |
| if (m.html && m.html.length > 0) { | |
| bodyWrapper.innerHTML = `<iframe id="mailIframe" title="Email Content"></iframe>`; | |
| const iframe = document.getElementById('mailIframe'); | |
| const doc = iframe.contentWindow.document; | |
| doc.open(); | |
| doc.write(`<style>body{font-family:'Inter',sans-serif;line-height:1.6;color:#333;}img{max-width:100%;height:auto;}a{color:#4f46e5;}</style>${m.html[0]}`); | |
| doc.close(); | |
| iframe.onload = () => { iframe.style.height = doc.body.scrollHeight + 'px'; }; | |
| } else { | |
| const textWithLinks = m.text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer" style="color:#4f46e5;text-decoration:underline;">$1</a>'); | |
| bodyWrapper.innerHTML = `<div class="p-6 bg-white h-full overflow-auto" style="color:#1e293b; line-height:1.6; white-space:pre-wrap; word-break:break-word;">${textWithLinks}</div>`; | |
| } | |
| } catch (err) { | |
| bodyWrapper.innerHTML = '<p class="text-red-500 text-center p-10">Lỗi tải nội dung thư. Vui lòng thử lại.</p>'; | |
| } | |
| } | |
| // Hàm tải tệp thực tế sử dụng Bearer Token | |
| async function downloadFile(url, filename) { | |
| try { | |
| const response = await fetch(url, { | |
| headers: { 'Authorization': `Bearer ${currentToken}` } | |
| }); | |
| const blob = await response.blob(); | |
| const link = document.createElement('a'); | |
| link.href = window.URL.createObjectURL(blob); | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } catch (err) { | |
| console.error("Lỗi tải tệp:", err); | |
| alert("Không thể tải tệp. Vui lòng kiểm tra kết nối."); | |
| } | |
| } | |
| function closeMail() { | |
| document.getElementById('mailModal').classList.add('hidden'); | |
| } | |
| function copyEmail() { | |
| const el = document.getElementById('emailAddr'); | |
| el.select(); | |
| document.execCommand('copy'); | |
| const toast = document.getElementById('copyToast'); | |
| toast.classList.remove('hidden'); | |
| setTimeout(() => toast.classList.add('hidden'), 2000); | |
| } | |
| window.onload = init; | |
| </script> | |
| </body> | |
| </html> | |