FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
lightweight
lightweightglimpse2-runbook-view-glimpse-save-login-react
Remix of lightweight/glimpse2-runbook-view-glimpse-save-login
Public
Like
glimpse2-runbook-view-glimpse-save-login-react
Home
Code
8
_townie
13
backend
7
frontend
9
shared
3
.vtignore
README.md
deno.json
H
main.tsx
Branches
2
Pull requests
Remixes
History
Environment variables
6
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
_townie
/
10-react.md
Code
/
_townie
/
10-react.md
Search
9/5/2025
10-react.md

Complete Implementation Instructions: React Bootstrap for Glimpse Endpoint (REVISED)

=================================================================================

Objective


Transform an existing JSON-only /glimpse/:id endpoint to serve a professional HTML page with React, while maintaining JSON API compatibility.

Prerequisites


  • Existing /glimpse/:id endpoint that returns JSON data
  • Hono-based backend with authentication middleware
  • User email available via c.get("userEmail") from auth context
  • LastLogin authentication system with /auth/logout endpoint

Implementation Steps


1. Create Frontend Directory Structure

/frontend/
├── glimpse.html
├── glimpse.tsx
├── components/
│   └── GlimpseView.tsx
└── README.md (update existing)

2. Create /frontend/glimpse.html

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>

3. Create /frontend/glimpse.tsx

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} />);

4. Create /frontend/components/GlimpseView.tsx

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>
  );
}

5. Update /frontend/README.md

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

6. Modify Glimpse Controller

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);
}

7. Add Frontend File Serving

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));

Key Features Delivered


✅ 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

Testing


  • Visit /glimpse/:id → Should show HTML page with layout
  • Visit /glimpse/:id?format=json → Should return JSON data
  • Verify user email appears in header
  • Verify logout link works
  • Verify React entry point loads correctly

This implementation provides a clean, maintainable React architecture while maintaining all existing functionality and adding a professional UI layer.

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.