This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Jeffrey Smith d9802df2af
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m51s
CI/CD / test-sqlite (push) Successful in 3m10s
CI/CD / build-and-deploy (push) Successful in 1m31s
Feat v0.6.15 user display audit (#50)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-04-01 14:19:48 +00:00

222 lines
7.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Team Activity Log — Surface Entry Point
*
* Demonstrates:
* - SDK boot + sw.* API access
* - Preact rendering into #extension-mount
* - Calling Starlark-backed ext API routes (/s/:slug/api/*)
* - sw.toast, sw.auth, sw.on (WS event subscription)
* - sw.userMenu for shell integration
* - Platform CSS variables for theming
*/
(async function () {
'use strict';
var mount = document.getElementById('extension-mount');
if (!mount) return;
// ── Boot SDK (extension surfaces don't auto-load it) ──
var base = window.__BASE__ || '';
var ver = window.__VERSION__ || '0';
var slug = (window.__MANIFEST__ || {}).id || 'team-activity-log';
try {
if (!window.preact) {
var { h, render } = await import(base + '/js/sw/vendor/preact.module.js');
var hooksModule = await import(base + '/js/sw/vendor/hooks.module.js');
var htmModule = await import(base + '/js/sw/vendor/htm.module.js');
window.preact = { h, render };
window.hooks = hooksModule;
window.html = htmModule.default.bind(h);
}
var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver);
await sdk.boot();
} catch (e) {
mount.innerHTML = '<p style="color:var(--danger);padding:24px;">SDK boot failed: ' + e.message + '</p>';
return;
}
var { html } = window;
var { useState, useEffect, useCallback, useRef } = hooks;
var { render } = preact;
// ── API helper: call Starlark backend ──
var API_BASE = base + '/s/' + slug + '/api';
async function api(method, path, body) {
var token = sw.auth._getToken();
var opts = {
method: method,
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
};
if (body) opts.body = JSON.stringify(body);
var resp = await fetch(API_BASE + path, opts);
var text = await resp.text();
try { return JSON.parse(text); } catch (_) { return { _raw: text, _status: resp.status }; }
}
// ── Components ─────────────────────────────
var CATEGORIES = ['note', 'update', 'decision', 'action-item'];
function App() {
var [entries, setEntries] = useState([]);
var [loading, setLoading] = useState(true);
var [filter, setFilter] = useState('');
var menuRef = useRef(null);
var load = useCallback(async function () {
var path = '/entries' + (filter ? '?category=' + encodeURIComponent(filter) : '');
var d = await api('GET', path);
setEntries(d.data || []);
setLoading(false);
}, [filter]);
useEffect(function () { load(); }, [load]);
// Mount user menu
useEffect(function () {
if (menuRef.current && sw.userMenu) {
sw.userMenu(menuRef.current, { placement: 'down-right' });
}
}, []);
async function addEntry(category, message) {
var user = sw.auth.user;
var result = await api('POST', '/entries', {
category: category,
message: message,
username: user ? (user.display_name || user.username) : '',
created_at: new Date().toISOString(),
});
if (result.error) {
sw.toast(result.error, 'error');
return;
}
sw.toast('Entry added', 'success');
load();
}
async function deleteEntry(id) {
await api('DELETE', '/entries/' + id);
sw.toast('Entry deleted', 'info');
load();
}
return html`
<div class="ext-team-activity-log-shell">
<header class="ext-team-activity-log-header">
<div class="ext-team-activity-log-header__left">
<h1 class="ext-team-activity-log-title">Activity Log</h1>
<span class="ext-team-activity-log-subtitle">Team activity tracker</span>
</div>
<div class="ext-team-activity-log-header__right">
<div ref=${menuRef}></div>
</div>
</header>
<${EntryForm} onSubmit=${addEntry} />
<div class="ext-team-activity-log-filters">
<button class="ext-team-activity-log-filter ${filter === '' ? 'ext-team-activity-log-active' : ''}"
onClick=${() => setFilter('')}>All</button>
${CATEGORIES.map(c => html`
<button key=${c}
class="ext-team-activity-log-filter ${filter === c ? 'ext-team-activity-log-active' : ''}"
onClick=${() => setFilter(c)}>
${c}
</button>
`)}
<span class="ext-team-activity-log-count">${entries.length} entries</span>
</div>
${loading
? html`<div class="ext-team-activity-log-loading">Loading…</div>`
: html`<${EntryList} entries=${entries} onDelete=${deleteEntry} />`
}
</div>
`;
}
function EntryForm({ onSubmit }) {
var [message, setMessage] = useState('');
var [category, setCategory] = useState('note');
var [submitting, setSubmitting] = useState(false);
async function submit(e) {
e.preventDefault();
if (!message.trim()) return;
setSubmitting(true);
await onSubmit(category, message.trim());
setMessage('');
setSubmitting(false);
}
return html`
<form class="ext-team-activity-log-form" onSubmit=${submit}>
<select class="ext-team-activity-log-form__select" value=${category}
onChange=${e => setCategory(e.target.value)}>
${CATEGORIES.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
</select>
<input class="ext-team-activity-log-form__input" type="text"
placeholder="What happened?"
value=${message}
onInput=${e => setMessage(e.target.value)}
disabled=${submitting} />
<button class="ext-team-activity-log-form__btn" type="submit" disabled=${submitting || !message.trim()}>
${submitting ? '…' : 'Log'}
</button>
</form>
`;
}
function EntryList({ entries, onDelete }) {
if (!entries.length) {
return html`<div class="ext-team-activity-log-empty">No entries yet. Log your first activity above.</div>`;
}
return html`
<div class="ext-team-activity-log-entries">
${entries.map(e => html`
<div key=${e.id} class="ext-team-activity-log-entry">
<div class="ext-team-activity-log-entry__header">
<span class="ext-team-activity-log-entry__category badge">${e.category}</span>
<span class="ext-team-activity-log-entry__user">${esc(e.username || 'Unknown')}</span>
<span class="ext-team-activity-log-entry__time">${timeAgo(e.created_at)}</span>
<button class="ext-team-activity-log-entry__delete" onClick=${() => onDelete(e.id)}
title="Delete">×</button>
</div>
<div class="ext-team-activity-log-entry__message">${esc(e.message)}</div>
</div>
`)}
</div>
`;
}
// ── Utilities ──────────────────────────────
function esc(s) {
var el = document.createElement('span');
el.textContent = String(s || '');
return el.innerHTML;
}
function timeAgo(iso) {
if (!iso) return '';
var ms = Date.now() - new Date(iso).getTime();
var sec = Math.floor(ms / 1000);
if (sec < 60) return 'just now';
var min = Math.floor(sec / 60);
if (min < 60) return min + 'm ago';
var hr = Math.floor(min / 60);
if (hr < 24) return hr + 'h ago';
var d = Math.floor(hr / 24);
return d + 'd ago';
}
// ── Mount ──────────────────────────────────
render(html`<${App} />`, mount);
console.log('[team-activity-log] Surface mounted');
})();