feat(revents): add events module scaffold
This commit is contained in:
parent
4cdba2e7de
commit
96a00a6f36
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* rEvents landing page — event aggregation & discovery.
|
||||||
|
*/
|
||||||
|
export function renderLanding(): string {
|
||||||
|
return `
|
||||||
|
<!-- Hero -->
|
||||||
|
<div class="rl-hero">
|
||||||
|
<span class="rl-tagline" style="color:#a78bfa;background:rgba(167,139,250,0.1);border-color:rgba(167,139,250,0.2)">
|
||||||
|
Event Aggregator
|
||||||
|
</span>
|
||||||
|
<h1 class="rl-heading" style="background:linear-gradient(to right,#a78bfa,#ec4899);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
|
||||||
|
All your events. One place.
|
||||||
|
</h1>
|
||||||
|
<p class="rl-subtitle">
|
||||||
|
Aggregate events from Luma, Meetup, iCal feeds, and more into a unified stream for your community.
|
||||||
|
</p>
|
||||||
|
<p class="rl-subtext">
|
||||||
|
rEvents is the <span style="color:#a78bfa;font-weight:600">event ingestion layer</span> for the rStack.
|
||||||
|
Connect external event platforms, parse unstructured text into structured events,
|
||||||
|
and keep your community's calendar in sync — automatically.
|
||||||
|
</p>
|
||||||
|
<div class="rl-cta-row">
|
||||||
|
<a href="https://demo.rspace.online/revents" class="rl-cta-primary" id="ml-primary"
|
||||||
|
style="background:linear-gradient(to right,#a78bfa,#ec4899);color:#0b1120">
|
||||||
|
Try the Demo
|
||||||
|
</a>
|
||||||
|
<a href="#features" class="rl-cta-secondary">Learn More</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Features (3-card grid) -->
|
||||||
|
<section class="rl-section" style="border-top:none" id="features">
|
||||||
|
<div class="rl-container">
|
||||||
|
<div class="rl-grid-3">
|
||||||
|
<div class="rl-card rl-card--center" style="padding:2rem">
|
||||||
|
<div class="rl-icon-box" style="background:rgba(167,139,250,0.12);font-size:1.5rem">
|
||||||
|
<span style="font-size:1.5rem">🔗</span>
|
||||||
|
</div>
|
||||||
|
<h3>Aggregate</h3>
|
||||||
|
<p>Pull events from Luma, Meetup, iCal feeds, and RSS into one unified stream. Sync on demand or on a schedule.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rl-card rl-card--center" style="padding:2rem">
|
||||||
|
<div class="rl-icon-box" style="background:rgba(236,72,153,0.12);font-size:1.5rem">
|
||||||
|
<span style="font-size:1.5rem">✨</span>
|
||||||
|
</div>
|
||||||
|
<h3>Parse</h3>
|
||||||
|
<p>Paste unstructured text — emails, messages, flyers — and extract structured event data with dates, locations, and more.</p>
|
||||||
|
</div>
|
||||||
|
<div class="rl-card rl-card--center" style="padding:2rem">
|
||||||
|
<div class="rl-icon-box" style="background:rgba(96,165,250,0.12);font-size:1.5rem">
|
||||||
|
<span style="font-size:1.5rem">📅</span>
|
||||||
|
</div>
|
||||||
|
<h3>Connect</h3>
|
||||||
|
<p>Push events to rCal, surface them in rInbox, coordinate across the entire rStack ecosystem seamlessly.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Supported Sources -->
|
||||||
|
<section class="rl-section">
|
||||||
|
<div class="rl-container">
|
||||||
|
<h2 class="rl-section-title" style="text-align:center;margin-bottom:2rem">Supported Sources</h2>
|
||||||
|
<div class="rl-grid-4">
|
||||||
|
<div class="rl-card rl-card--center" style="padding:1.5rem">
|
||||||
|
<div style="font-size:2rem;margin-bottom:0.5rem">🎄</div>
|
||||||
|
<h3 style="font-size:1rem">Luma</h3>
|
||||||
|
<p style="font-size:0.8rem">lu.ma calendar events</p>
|
||||||
|
</div>
|
||||||
|
<div class="rl-card rl-card--center" style="padding:1.5rem">
|
||||||
|
<div style="font-size:2rem;margin-bottom:0.5rem">🏅</div>
|
||||||
|
<h3 style="font-size:1rem">Meetup</h3>
|
||||||
|
<p style="font-size:0.8rem">Meetup.com groups</p>
|
||||||
|
</div>
|
||||||
|
<div class="rl-card rl-card--center" style="padding:1.5rem">
|
||||||
|
<div style="font-size:2rem;margin-bottom:0.5rem">📅</div>
|
||||||
|
<h3 style="font-size:1rem">iCal / ICS</h3>
|
||||||
|
<p style="font-size:0.8rem">Any .ics feed URL</p>
|
||||||
|
</div>
|
||||||
|
<div class="rl-card rl-card--center" style="padding:1.5rem">
|
||||||
|
<div style="font-size:2rem;margin-bottom:0.5rem">✍</div>
|
||||||
|
<h3 style="font-size:1rem">Manual / Text</h3>
|
||||||
|
<p style="font-size:0.8rem">Paste & parse anything</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
/**
|
||||||
|
* rEvents module — event aggregation & parsing.
|
||||||
|
*
|
||||||
|
* Aggregates events from Luma, Meetup, iCal feeds, and unstructured text.
|
||||||
|
* The functional app runs as a standalone Next.js container (revents-online:3000)
|
||||||
|
* and is embedded via externalApp iframe in rSpace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { renderShell, renderExternalAppShell, escapeHtml } from "../../server/shell";
|
||||||
|
import { getModuleInfoList } from "../../shared/module";
|
||||||
|
import type { RSpaceModule } from "../../shared/module";
|
||||||
|
import { renderLanding } from "./landing";
|
||||||
|
|
||||||
|
const REVENTS_URL = process.env.REVENTS_URL || "http://revents-online:3000";
|
||||||
|
const REVENTS_PUBLIC_URL = process.env.REVENTS_PUBLIC_URL || "https://revents.online";
|
||||||
|
|
||||||
|
const routes = new Hono();
|
||||||
|
|
||||||
|
// ── Proxy API calls to revents-online ──
|
||||||
|
|
||||||
|
routes.get("/api/events", async (c) => {
|
||||||
|
const qs = new URL(c.req.url).search;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${REVENTS_URL}/api/events${qs}`);
|
||||||
|
const data = await res.json();
|
||||||
|
return c.json(data);
|
||||||
|
} catch {
|
||||||
|
return c.json({ count: 0, results: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.get("/api/sources", async (c) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${REVENTS_URL}/api/sources`);
|
||||||
|
const data = await res.json();
|
||||||
|
return c.json(data);
|
||||||
|
} catch {
|
||||||
|
return c.json([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.post("/api/parse", async (c) => {
|
||||||
|
try {
|
||||||
|
const body = await c.req.json();
|
||||||
|
const res = await fetch(`${REVENTS_URL}/api/parse`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
return c.json(data);
|
||||||
|
} catch (e: any) {
|
||||||
|
return c.json({ error: e.message }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Explore page (embedded) ──
|
||||||
|
|
||||||
|
routes.get("/explore", (c) => {
|
||||||
|
const space = c.req.param("space") || "demo";
|
||||||
|
return c.html(renderExternalAppShell({
|
||||||
|
title: `Explore Events — rEvents | rSpace`,
|
||||||
|
moduleId: "revents",
|
||||||
|
spaceSlug: space,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
appUrl: `${REVENTS_PUBLIC_URL}/explore`,
|
||||||
|
appName: "rEvents",
|
||||||
|
theme: "dark",
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Sources page (embedded) ──
|
||||||
|
|
||||||
|
routes.get("/sources", (c) => {
|
||||||
|
const space = c.req.param("space") || "demo";
|
||||||
|
return c.html(renderExternalAppShell({
|
||||||
|
title: `Event Sources — rEvents | rSpace`,
|
||||||
|
moduleId: "revents",
|
||||||
|
spaceSlug: space,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
appUrl: `${REVENTS_PUBLIC_URL}/sources`,
|
||||||
|
appName: "rEvents",
|
||||||
|
theme: "dark",
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Hub page ──
|
||||||
|
|
||||||
|
routes.get("/", (c) => {
|
||||||
|
const space = c.req.param("space") || "demo";
|
||||||
|
const base = `/${escapeHtml(space)}/revents`;
|
||||||
|
return c.html(renderShell({
|
||||||
|
title: `rEvents — ${space} | rSpace`,
|
||||||
|
moduleId: "revents",
|
||||||
|
spaceSlug: space,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
styles: `<style>
|
||||||
|
.rs-hub{max-width:720px;margin:2rem auto;padding:0 1.5rem}
|
||||||
|
.rs-hub h1{font-size:1.8rem;margin-bottom:.5rem;color:var(--rs-text-primary)}
|
||||||
|
.rs-hub>p{color:var(--rs-text-secondary);margin-bottom:2rem}
|
||||||
|
.rs-nav{display:flex;flex-direction:column;gap:1rem}
|
||||||
|
.rs-nav a{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.5rem;border-radius:12px;background:var(--rs-bg-surface);border:1px solid var(--rs-border);text-decoration:none;color:inherit;transition:border-color .15s,background .15s}
|
||||||
|
.rs-nav a:hover{border-color:var(--rs-accent);background:var(--rs-bg-hover)}
|
||||||
|
.rs-nav .nav-icon{font-size:2rem;flex-shrink:0}
|
||||||
|
.rs-nav .nav-body h3{margin:0 0 .25rem;font-size:1.1rem;color:var(--rs-text-primary)}
|
||||||
|
.rs-nav .nav-body p{margin:0;font-size:.85rem;color:var(--rs-text-secondary)}
|
||||||
|
@media(max-width:600px){.rs-hub{margin:1rem auto;padding:0 .75rem}.rs-nav a{padding:1rem;gap:.75rem}.rs-nav .nav-icon{font-size:1.5rem}}
|
||||||
|
</style>`,
|
||||||
|
body: `<div class="rs-hub">
|
||||||
|
<h1>rEvents</h1>
|
||||||
|
<p>Aggregate events from Luma, Meetup, iCal, and more</p>
|
||||||
|
<nav class="rs-nav">
|
||||||
|
<a href="${base}/explore">
|
||||||
|
<span class="nav-icon">🔍</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Explore Events</h3>
|
||||||
|
<p>Browse and search upcoming events from all connected sources</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="${base}/sources">
|
||||||
|
<span class="nav-icon">🔗</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Event Sources</h3>
|
||||||
|
<p>Connect Luma, Meetup, iCal feeds and manage sync</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="${REVENTS_PUBLIC_URL}" target="_blank" rel="noopener">
|
||||||
|
<span class="nav-icon">🎪</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Open rEvents</h3>
|
||||||
|
<p>Full standalone app at revents.online</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>`,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
export const eventsModule: RSpaceModule = {
|
||||||
|
id: "revents",
|
||||||
|
name: "rEvents",
|
||||||
|
icon: "🎪",
|
||||||
|
description: "Event aggregation & discovery from Luma, Meetup, iCal and more",
|
||||||
|
scoping: { defaultScope: "space", userConfigurable: true },
|
||||||
|
routes,
|
||||||
|
standaloneDomain: "revents.online",
|
||||||
|
landingPage: renderLanding,
|
||||||
|
externalApp: { url: REVENTS_PUBLIC_URL, name: "rEvents" },
|
||||||
|
outputPaths: [
|
||||||
|
{ path: "explore", name: "Explore", icon: "🔍", description: "Browse upcoming events" },
|
||||||
|
{ path: "sources", name: "Sources", icon: "🔗", description: "Connected event sources" },
|
||||||
|
],
|
||||||
|
feeds: [
|
||||||
|
{
|
||||||
|
id: "events",
|
||||||
|
name: "Events",
|
||||||
|
kind: "data",
|
||||||
|
description: "Aggregated events from external platforms and manual input",
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
acceptsFeeds: ["data"],
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue