All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 5s
- schema.sql: card_text table (single-row, id=1) with content + updated_at
- server.js: Express + pg server with CRUD API:
GET /api/text — read current text
PUT /api/text — create / update text
DELETE /api/text — reset to default lorem ipsum
- package.json: express + pg dependencies (converts project to Node type)
- index.html: card now loads text from DB, inline Edit / Save / Cancel
controls, Reset button with confirm dialog, last-updated timestamp
DB: testapp (appuser) on localhost:5432
Closes #8
245 lines
8.7 KiB
HTML
245 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Deploy Test</title>
|
|
<style>
|
|
:root[data-theme="light"] {
|
|
--bg: #f5f5f5;
|
|
--fg: #111111;
|
|
--card: #ffffff;
|
|
--border: #ddd;
|
|
--accent: #4a6ef5;
|
|
--btn-bg: #111;
|
|
--btn-fg: #fff;
|
|
--btn-hover: #333;
|
|
--input-bg: #f9f9f9;
|
|
--meta: #888;
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--bg: #0d0d0d;
|
|
--fg: #e0e0e0;
|
|
--card: #161616;
|
|
--border: #2a2a2a;
|
|
--accent: #7c9ef5;
|
|
--btn-bg: #2a2a2a;
|
|
--btn-fg: #e0e0e0;
|
|
--btn-hover: #3a3a3a;
|
|
--input-bg: #0d0d0d;
|
|
--meta: #555;
|
|
}
|
|
*, *::before, *::after { box-sizing: border-box; }
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
font-family: system-ui, sans-serif;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
gap: 1rem;
|
|
transition: background .2s, color .2s;
|
|
margin: 0;
|
|
padding: 1rem;
|
|
}
|
|
h1 { margin: 0; }
|
|
p { max-width: 520px; text-align: center; line-height: 1.6; }
|
|
code { color: var(--accent); }
|
|
#theme-toggle {
|
|
position: fixed;
|
|
top: 1rem;
|
|
right: 1rem;
|
|
background: var(--btn-bg);
|
|
color: var(--btn-fg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: .45rem .85rem;
|
|
font-size: .85rem;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: background .15s;
|
|
}
|
|
#theme-toggle:hover { background: var(--btn-hover); }
|
|
|
|
/* Card */
|
|
.card {
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
padding: 1.4rem 1.8rem;
|
|
width: min(520px, 96vw);
|
|
line-height: 1.6;
|
|
box-shadow: 0 0 0 1px rgba(34, 197, 94, .15),
|
|
0 0 18px 4px rgba(34, 197, 94, .25),
|
|
0 0 40px 8px rgba(34, 197, 94, .1);
|
|
transition: background .2s, border-color .2s, box-shadow .2s;
|
|
}
|
|
:root[data-theme="dark"] .card {
|
|
box-shadow: 0 0 0 1px rgba(34, 197, 94, .2),
|
|
0 0 20px 5px rgba(34, 197, 94, .3),
|
|
0 0 50px 10px rgba(34, 197, 94, .12);
|
|
}
|
|
#card-text { margin: 0 0 .9rem; }
|
|
#card-meta { color: var(--meta); font-size: .72rem; margin-bottom: .7rem; min-height: 1rem; }
|
|
.card-actions { display: flex; gap: .5rem; justify-content: flex-end; }
|
|
.card-btn {
|
|
background: var(--btn-bg);
|
|
color: var(--btn-fg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 7px;
|
|
padding: .35rem .75rem;
|
|
font-size: .8rem;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: background .15s;
|
|
}
|
|
.card-btn:hover { background: var(--btn-hover); }
|
|
.card-btn:disabled { opacity: .4; cursor: default; }
|
|
.card-btn-danger { color: #f57c7c; border-color: rgba(245,124,124,.3); }
|
|
.card-btn-danger:hover { background: rgba(245,124,124,.1); }
|
|
#edit-form { display: none; flex-direction: column; gap: .6rem; margin-top: .5rem; }
|
|
#edit-form.open { display: flex; }
|
|
#edit-input {
|
|
width: 100%; min-height: 90px; background: var(--input-bg); color: var(--fg);
|
|
border: 1px solid var(--border); border-radius: 8px; padding: .55rem .7rem;
|
|
font-family: inherit; font-size: .9rem; resize: vertical; outline: none;
|
|
transition: border-color .15s;
|
|
}
|
|
#edit-input:focus { border-color: var(--accent); }
|
|
#card-status { font-size: .75rem; color: var(--meta); min-height: 1rem; text-align: right; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<button id="theme-toggle">🌙 Dark mode</button>
|
|
<h1>🚀 Deploy verified ✅</h1>
|
|
|
|
<div class="card" id="text-card">
|
|
<p id="card-text">Loading…</p>
|
|
<div id="card-meta"></div>
|
|
<div class="card-actions" id="card-actions">
|
|
<button class="card-btn" id="edit-btn">✏️ Edit</button>
|
|
<button class="card-btn card-btn-danger" id="reset-btn">↺ Reset</button>
|
|
</div>
|
|
<div id="edit-form">
|
|
<textarea id="edit-input" placeholder="Enter text…"></textarea>
|
|
<div style="display:flex; gap:.5rem; justify-content:flex-end; align-items:center;">
|
|
<span id="card-status"></span>
|
|
<button class="card-btn" id="cancel-btn">✕ Cancel</button>
|
|
<button class="card-btn" id="save-btn">💾 Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p>Committed by OpenClaw at <code>2026-03-13 10:11:00 UTC</code></p>
|
|
|
|
<script>
|
|
// ── Theme ──────────────────────────────────────────────────────────────
|
|
(function() {
|
|
var root = document.documentElement;
|
|
var btn = document.getElementById('theme-toggle');
|
|
var saved = localStorage.getItem('theme') || 'light';
|
|
setTheme(saved);
|
|
btn.addEventListener('click', function() {
|
|
setTheme(root.getAttribute('data-theme') === 'light' ? 'dark' : 'light');
|
|
});
|
|
function setTheme(t) {
|
|
root.setAttribute('data-theme', t);
|
|
localStorage.setItem('theme', t);
|
|
btn.textContent = t === 'light' ? '🌙 Dark mode' : '☀️ Light mode';
|
|
}
|
|
})();
|
|
|
|
// ── CRUD card ──────────────────────────────────────────────────────────
|
|
(function() {
|
|
var BASE = '/test';
|
|
var textEl = document.getElementById('card-text');
|
|
var metaEl = document.getElementById('card-meta');
|
|
var editForm = document.getElementById('edit-form');
|
|
var editInput = document.getElementById('edit-input');
|
|
var statusEl = document.getElementById('card-status');
|
|
var editBtn = document.getElementById('edit-btn');
|
|
var saveBtn = document.getElementById('save-btn');
|
|
var cancelBtn = document.getElementById('cancel-btn');
|
|
var resetBtn = document.getElementById('reset-btn');
|
|
|
|
function setMeta(updated_at) {
|
|
metaEl.textContent = updated_at
|
|
? 'Last updated: ' + new Date(updated_at).toLocaleString()
|
|
: '';
|
|
}
|
|
|
|
function setStatus(msg, color) {
|
|
statusEl.textContent = msg;
|
|
statusEl.style.color = color || '';
|
|
if (msg) setTimeout(function() { statusEl.textContent = ''; }, 2500);
|
|
}
|
|
|
|
// READ
|
|
function load() {
|
|
fetch(BASE + '/api/text')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(d) {
|
|
textEl.textContent = d.content || '';
|
|
setMeta(d.updated_at);
|
|
})
|
|
.catch(function() { textEl.textContent = '(failed to load)'; });
|
|
}
|
|
load();
|
|
|
|
// Edit mode toggle
|
|
editBtn.addEventListener('click', function() {
|
|
editInput.value = textEl.textContent;
|
|
editForm.classList.add('open');
|
|
editBtn.style.display = 'none';
|
|
resetBtn.style.display = 'none';
|
|
editInput.focus();
|
|
});
|
|
|
|
cancelBtn.addEventListener('click', function() {
|
|
editForm.classList.remove('open');
|
|
editBtn.style.display = '';
|
|
resetBtn.style.display = '';
|
|
});
|
|
|
|
// UPDATE
|
|
saveBtn.addEventListener('click', function() {
|
|
var content = editInput.value.trim();
|
|
if (!content) { setStatus('Cannot be empty.', '#f57c7c'); return; }
|
|
saveBtn.disabled = true;
|
|
fetch(BASE + '/api/text', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ content: content })
|
|
}).then(function(r) { return r.json(); }).then(function(d) {
|
|
if (d.error) { setStatus('Error: ' + d.error, '#f57c7c'); saveBtn.disabled = false; return; }
|
|
textEl.textContent = d.content;
|
|
setMeta(d.updated_at);
|
|
editForm.classList.remove('open');
|
|
editBtn.style.display = '';
|
|
resetBtn.style.display = '';
|
|
setStatus('Saved!', '#6fcf6f');
|
|
saveBtn.disabled = false;
|
|
}).catch(function() { setStatus('Network error.', '#f57c7c'); saveBtn.disabled = false; });
|
|
});
|
|
|
|
// DELETE (reset)
|
|
resetBtn.addEventListener('click', function() {
|
|
if (!confirm('Reset to default text?')) return;
|
|
resetBtn.disabled = true;
|
|
fetch(BASE + '/api/text', { method: 'DELETE' })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(d) {
|
|
textEl.textContent = d.content;
|
|
setMeta(d.updated_at);
|
|
setStatus('Reset!', '#6fcf6f');
|
|
resetBtn.disabled = false;
|
|
}).catch(function() { setStatus('Network error.', '#f57c7c'); resetBtn.disabled = false; });
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
<script src="https://dev.bl.pixeldev.eu/feedback-tool/widget.js" data-repo="pixeldev/test"></script>
|
|
</body>
|
|
</html>
|