mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
8d1f707557
Widget plugins can now declare capabilities.widget.slot 'hero' to render as a transparent, click-through overlay sitting on the boarding-pass bar's top edge (migration 157 persists capabilities; manifest validation server+SDK, feed exposes the slot, dashboard mounts hero frames above the pass). Sidebar stays the default slot. Replaces the trip-countdown example with Koffi, the TREK mascot: an animated suitcase with a 14-state behavior engine driven by real trip data — walking, waving, napping, trolley rolls, passport-stamp stickers, a split-flap luggage- tag countdown under 7 days, and sunglasses while the trip runs. Validated by the SDK suite like any author plugin; published as mauriceboe/trek-plugin-koffi in the registry. Migrations 156/157 follow the idempotent ALTER pattern (the reconciliation test re-runs everything from v135, so duplicate-column must stay non-fatal).
43 lines
1.6 KiB
JavaScript
43 lines
1.6 KiB
JavaScript
// Koffi — the TREK suitcase mascot (hero-slot widget).
|
|
//
|
|
// One route: GET /state?tripId= → { days, ongoing, title }. The mascot's mood is
|
|
// driven by how close the trip is. The host membership-checks the trip against
|
|
// the acting user, so Trekki can only ever see a trip that user can see.
|
|
|
|
const DAY_MS = 86400000;
|
|
|
|
function midnight(iso) {
|
|
const d = new Date(iso + 'T00:00:00');
|
|
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
|
}
|
|
|
|
module.exports = {
|
|
routes: [
|
|
{
|
|
method: 'GET',
|
|
path: '/state',
|
|
auth: true,
|
|
async handler(req, ctx) {
|
|
const json = { 'content-type': 'application/json' };
|
|
const tripId = Number(req.query.tripId);
|
|
if (!tripId) return { status: 200, headers: json, body: JSON.stringify({ days: null, ongoing: false, title: null }) };
|
|
|
|
const t = await ctx.trips.getById(tripId, req.user.id);
|
|
if (!t) return { status: 200, headers: json, body: JSON.stringify({ days: null, ongoing: false, title: null }) };
|
|
|
|
const now = new Date();
|
|
now.setHours(0, 0, 0, 0);
|
|
const start = t.start_date ? midnight(t.start_date) : null;
|
|
const end = t.end_date ? midnight(t.end_date) : null;
|
|
|
|
const ongoing = start !== null && start <= now.getTime() && (end === null || now.getTime() <= end);
|
|
const days = !ongoing && start !== null && start >= now.getTime()
|
|
? Math.round((start - now.getTime()) / DAY_MS)
|
|
: null;
|
|
|
|
return { status: 200, headers: json, body: JSON.stringify({ days, ongoing, title: t.title ?? null }) };
|
|
},
|
|
},
|
|
],
|
|
};
|