=================================================================================
Transform an existing JSON-only /glimpse/:id
endpoint to serve a professional HTML page with React, while maintaining JSON API compatibility.
/glimpse/:id
endpoint that returns JSON datac.get("userEmail")
from auth context/auth/logout
endpoint/frontend/
├── glimpse.html
├── glimpse.tsx
├── components/
│ └── GlimpseView.tsx
└── README.md (update existing)
Clean HTML shell that loads the React entry point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glimpse</title>
<script src="https://cdn.twind.style" crossorigin></script>
<script src="https://esm.town/v/std/catch"></script>
</head>
<body class="h-screen bg-gray-50">
<div id="root"></div>
<script type="module" src="/frontend/glimpse.tsx"></script>
</body>
</html>
React entry point that bootstraps the application:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
import React from "https://esm.sh/react@18.2.0?deps=react@18.2.0";
import { createRoot } from "https://esm.sh/react-dom@18.2.0/client?deps=react@18.2.0,react-dom@18.2.0";
import { GlimpseView } from "./components/GlimpseView.tsx";
// Get data injected by the server
const data = (window as any).__INITIAL_DATA__;
const userEmail = (window as any).__USER_EMAIL__;
// Render the application
const root = createRoot(document.getElementById('root')!);
root.render(<GlimpseView data={data} userEmail={userEmail} />);
Pure React component for displaying glimpse data:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
import React from "https://esm.sh/react@18.2.0?deps=react@18.2.0";
interface GlimpseViewProps {
data: any;
userEmail?: string;
}
export function GlimpseView({ data, userEmail }: GlimpseViewProps) {
return (
<div className="h-screen flex flex-col">
{/* Header */}
<header className="bg-white border-b px-6 py-4 flex justify-between items-center">
<h1 className="text-xl font-semibold text-gray-900">Glimpse</h1>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">{userEmail || 'user@example.com'}</span>
<a href="/auth/logout" className="text-sm text-blue-600 hover:text-blue-800">
Logout
</a>
</div>
</header>
{/* Main layout with sidenav and content */}
<div className="flex flex-1 overflow-hidden">
{/* Sidenav */}
<nav className="w-64 bg-white border-r p-4">
<div className="text-sm font-medium text-gray-900 mb-2">Navigation</div>
<ul className="space-y-1">
<li>
<a href="#" className="block px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
Dashboard
</a>
</li>
<li>
<a href="#" className="block px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded">
Current Glimpse
</a>
</li>
</ul>
</nav>
{/* Main content */}
<main className="flex-1 p-6 overflow-auto">
<div className="max-w-full">
<h2 className="text-lg font-medium text-gray-900 mb-4">Glimpse Data</h2>
<pre className="bg-white border rounded-lg p-4 text-sm overflow-auto whitespace-pre-wrap break-words">
{JSON.stringify(data, null, 2)}
</pre>
</div>
</main>
</div>
{/* Footer */}
<footer className="bg-white border-t px-6 py-3">
<p className="text-xs text-gray-500">© 2024 Glimpse Platform</p>
</footer>
</div>
);
}
Replace existing content with:
# Frontend
Client-side files that render in the browser.
## Structure
- `glimpse.html` - Clean HTML shell that loads React entry point
- `glimpse.tsx` - React bootstrap/entry point that gathers data and initializes the app
- `components/` - React components
- `GlimpseView.tsx` - Component for displaying JSON data in glimpse pages
## Glimpse Pages
The glimpse functionality uses a 3-layer architecture:
1. **HTML Shell** (`glimpse.html`) - Minimal HTML that loads the React entry point
2. **React Bootstrap** (`glimpse.tsx`) - Gathers server-injected data and renders the main component
3. **UI Component** (`GlimpseView.tsx`) - Pure React component that receives data as props
### Data Flow
1. Backend fetches cached or live Notion data
2. Data is injected into HTML template as `window.__INITIAL_DATA__`
3. React entry point reads the data and passes it to the component
4. Component renders the data in a `<pre>` tag for JSON display
## API Compatibility
The glimpse endpoint maintains backward compatibility:
- Default: Serves HTML page with React
- `?format=json` or `Accept: application/json` header: Returns JSON data
## Future Enhancements
When ready to format Notion data as HTML instead of JSON:
1. Modify the `GlimpseView` component in `components/GlimpseView.tsx`
2. Add proper Notion block rendering logic
3. The entry point (`glimpse.tsx`) handles data gathering, so UI changes only affect the component
In /backend/controllers/glimpse.controller.ts
:
Add imports:
import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
Replace the entire glimpseHandler
function:
export async function glimpseHandler(c: Context) {
const id = c.req.param("id");
if (!id) {
return c.json({ error: "Demo ID is required" }, 400);
}
// Get the data (cached or live)
let notionData;
// First try to get cached data
try {
const cachedData = await blob.getJSON(blobKeyForDemos(id));
if (cachedData) {
console.log(`Serving cached data for glimpse ID: ${id}`);
notionData = cachedData;
}
} catch (error) {
console.log(`No cached data found for glimpse ID: ${id}, falling back to live data`);
}
// Cache miss - fall back to live Notion data
if (!notionData) {
const result = await getGlimpseData(id);
if (!result.success) {
return c.json({ error: result.error, details: result.details }, result.status);
}
notionData = result.data;
}
// Check if they want JSON (for API compatibility)
if (c.req.header("accept")?.includes("application/json") || c.req.query("format") === "json") {
return c.json(notionData);
}
// Otherwise serve the React page
let html = await readFile("/frontend/glimpse.html", import.meta.url);
// Get user email from auth context
const userEmail = c.get("userEmail");
const dataScript = `<script>
window.__INITIAL_DATA__ = ${JSON.stringify(notionData)};
window.__USER_EMAIL__ = ${JSON.stringify(userEmail)};
</script>`;
html = html.replace("</head>", `${dataScript}</head>`);
return c.html(html);
}
In your main Hono app (likely /main.tsx
or similar), add frontend file serving:
Add after existing route mounts:
// Serve frontend files
app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
✅ Clean Architecture - Proper separation between HTML shell, React bootstrap, and UI components
✅ Professional Tailwind Layout - Header, sidenav, main content, footer
✅ Server-side Data Injection - No extra API calls needed
✅ User Context - Shows authenticated user's email in header
✅ Backward Compatibility - JSON API still works with ?format=json
✅ Proper Authentication Integration - Uses /auth/logout
endpoint
✅ Clean JSON Display - Formatted in scrollable <pre>
tag
✅ Minimal Tailwind Classes - Only essential styling
✅ React 18.2.0 - Properly configured with pinned versions
✅ Error Handling - Includes Val Town's error capture script
✅ Maintainable Code - Single source of truth for UI logic
/glimpse/:id
→ Should show HTML page with layout/glimpse/:id?format=json
→ Should return JSON dataThis implementation provides a clean, maintainable React architecture while maintaining all existing functionality and adding a professional UI layer.