Something went wrong. Try again.
A simple tool that syncs your buffer queue to your calendar app buffer2ical.wisp.place
Something went wrong. Try again.
buffer2ical dashboard.html
6.5 kB · 182 lines
HTML
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Dashboard - Buffer2iCal</title><link rel="icon" type="image/jpeg" href="/media/favicon.jpg"><script src="https://kit.fontawesome.com/8f1701cd51.js" crossorigin="anonymous"></script><link rel="stylesheet" href="css/core.css"></head><body> <div class="container"> <h1>Buffer2iCal</h1> <p id="loading"><i class="fa-solid fa-spinner fa-spin-snap-4"></i> Loading...</p>
<div id="app" hidden> <p id="demoNotice" class="highlight" hidden>Demo mode — example data only. No account changes can be made.</p> <p>Logged in as: <strong id="name"></strong> (<span id="email"></span>)</p> <div class="row"> <button id="refreshAccount">Refresh account</button> <span id="accountStatus" role="status" aria-live="polite"></span> </div> <p><a href="#" id="logout">Sign out</a></p>
<hr> <h2><i class="fa-solid fa-water"></i> Your iCal Feed</h2> <p>Add this URL to your calendar:</p> <code id="feedUrl"></code> <div class="row"> <button id="copy">Copy URL</button> <button id="refresh">Force Refresh</button> <a id="test" href="#" target="_blank">Test feed</a> </div> <p id="status" style="margin-top:0.75rem;"></p>
<hr> <h2><i class="fa-solid fa-chart-column"></i>Stats</h2> <ul> <li>Total posts: <span id="total"></span></li> <li>Scheduled: <span id="scheduled"></span></li> <li>Drafts: <span id="drafts"></span></li> <li>Channels: <span id="profiles"></span></li> <li>Last sync: <span id="lastSync"></span></li> <li>Status: <span id="state"></span></li> </ul>
<hr> <h2><i class="fa-solid fa-message"></i>Recent Posts</h2> <div id="recent"></div>
<hr> <h2><i class="fa-solid fa-exclamation-triangle"></i> Danger Zone</h2> <button class="danger" id="delete">Disconnect Buffer2iCal</button> </div> </div>
<script src="js/config.js"></script> <script> const $ = id => document.getElementById(id); const api = (path, opts = {}) => fetch(`${API_URL}${path}`, { credentials: 'include', ...opts }); const devMode = new URLSearchParams(location.search).get('dev') === 'true';
async function load() { let data; if (devMode) { data = { user: { name: 'Alex Morgan', email: 'alex@example.com', lastSync: new Date().toISOString() }, feedUrl: 'https://example.com/feeds/demo.ics', stats: { totalEvents: 24, scheduled: 8, drafts: 3, profiles: 2, isStale: false, recentPosts: [ { text: 'Launching our new spring collection this week! Take a first look at what is coming.', status: 'scheduled' }, { text: 'A behind-the-scenes look at how our team brings every idea to life.', status: 'scheduled' }, { text: 'Quick tip: make your content calendar work for you with a little planning.', status: 'draft' } ] } }; $('demoNotice').hidden = false; $('logout').hidden = true; $('refresh').disabled = true; $('refreshAccount').disabled = true; $('delete').disabled = true; } else { const res = await api('/api/me'); if (res.status === 401) { location.replace('/app'); return; } data = await res.json(); } const { user, feedUrl, stats } = data;
$('name').textContent = user.name; $('email').textContent = user.email || 'no email'; $('feedUrl').textContent = feedUrl; $('test').href = feedUrl; $('total').textContent = stats.totalEvents || 0; $('scheduled').textContent = stats.scheduled || 0; $('drafts').textContent = stats.drafts || 0; $('profiles').textContent = stats.profiles || 0; $('lastSync').textContent = user.lastSync ? new Date(user.lastSync).toLocaleString() : 'Never'; $('state').textContent = stats.isStale ? 'Needs refresh' : 'Up to date';
const recent = $('recent'); recent.replaceChildren(); if (!stats.recentPosts?.length) { recent.textContent = 'No posts found'; } else { for (const p of stats.recentPosts) { const el = document.createElement('p'); el.textContent = p.text.slice(0, 60) + (p.text.length > 60 ? '...' : '') + ` (${p.status})`; recent.appendChild(el); } }
$('loading').hidden = true; $('app').hidden = false; }
$('copy').onclick = () => navigator.clipboard.writeText($('feedUrl').textContent);
$('refreshAccount').onclick = async () => { const button = $('refreshAccount'); const status = $('accountStatus'); button.disabled = true; status.textContent = 'Refreshing account...'; try { const res = await api('/api/account/refresh', { method: 'POST' }); const data = await res.json(); if (!res.ok || !data.success) { throw new Error(data.error || `Request failed (${res.status})`); } $('name').textContent = data.user.name; $('email').textContent = data.user.email || 'no email'; status.textContent = 'Account details refreshed.'; } catch (e) { status.textContent = 'Failed to refresh account: ' + e.message; } finally { button.disabled = false; } };
$('refresh').onclick = async () => { $('status').textContent = 'Refreshing...'; try { const res = await api('/api/refresh', { method: 'POST' }); const data = await res.json(); $('status').textContent = data.success ? 'Done!' : 'Failed: ' + data.error; if (data.success) setTimeout(load, 500); } catch (e) { $('status').textContent = 'Error: ' + e.message; } };
$('logout').onclick = async e => { e.preventDefault(); await api('/api/logout', { method: 'POST' }); location.href = '/'; };
$('delete').onclick = async () => { if (!confirm('Disconnect Buffer2iCal? This will stop syncing with connected calendars and delete the iCal API route.')) return; await api('/api/user', { method: 'DELETE' }); location.href = '/'; };
load(); </script></body></html>