Skip to main content

Frontend Getting Started

Learn how to integrate Boards into your React applications using the frontend hooks library.

Overview

The Boards frontend is built with a hooks-first philosophy, providing React hooks rather than prescriptive UI components. This approach gives you maximum flexibility to build your own interface while leveraging Boards' powerful backend.

Architecture

  • React 18+ with hooks and concurrent features
  • TypeScript for type safety
  • Next.js compatible (App Router and Pages Router)
  • Real-time updates via Server-Sent Events (SSE)
  • Optimistic updates for better UX
  • Provider pattern for configuration

Installation

Install the frontend hooks package:

# With pnpm (recommended)
pnpm add @weirdfingers/boards

# With npm
npm install @weirdfingers/boards

# With yarn
yarn add @weirdfingers/boards

Basic Setup

1. Configure the Provider

Wrap your app with the BoardsProvider:

// app/layout.tsx (Next.js App Router)
import { BoardsProvider } from "@weirdfingers/boards";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<BoardsProvider apiUrl="http://localhost:8088" tenant="your-tenant-id">
{children}
</BoardsProvider>
</body>
</html>
);
}
// pages/_app.tsx (Next.js Pages Router)
import { BoardsProvider } from "@weirdfingers/boards";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
return (
<BoardsProvider apiUrl="http://localhost:8088" tenant="your-tenant-id">
<Component {...pageProps} />
</BoardsProvider>
);
}

2. Authentication Setup

Configure authentication with your preferred provider:

// With Supabase
import { BoardsProvider } from "@weirdfingers/boards";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

export default function RootLayout({ children }) {
return (
<BoardsProvider
apiUrl="http://localhost:8088"
tenant="your-tenant-id"
auth={{
type: "supabase",
client: supabase,
}}
>
{children}
</BoardsProvider>
);
}

Core Hooks

useBoards

Manage boards (collections) of generated content:

import { useBoards } from "@weirdfingers/boards";

function BoardsList() {
const { boards, createBoard, updateBoard, deleteBoard, isLoading } =
useBoards();

const handleCreateBoard = async () => {
await createBoard({
title: "My New Board",
description: "A collection of AI-generated content",
});
};

if (isLoading) return <div>Loading boards...</div>;

return (
<div>
<button onClick={handleCreateBoard}>Create Board</button>
{boards.map((board) => (
<div key={board.id}>
<h3>{board.title}</h3>
<p>{board.description}</p>
</div>
))}
</div>
);
}

useBoard

Load and manage a single board with its generations:

import { useBoard } from "@weirdfingers/boards";

function BoardDetail({ boardId }) {
const { board, updateBoard, deleteBoard, isLoading } = useBoard(boardId);

if (isLoading) return <div>Loading board...</div>;
if (!board) return <div>Board not found</div>;

return (
<div>
<h2>{board.title}</h2>
<p>{board.description}</p>
<button onClick={() => updateBoard({ title: "Updated Title" })}>
Update
</button>
<button onClick={deleteBoard}>Delete</button>
</div>
);
}

useGeneration

Handle AI content generation with real-time progress:

import { useGeneration } from "@weirdfingers/boards";

function GenerationDetail({ generationId }) {
const { generation, isLoading } = useGeneration(generationId);

if (isLoading) return <div>Loading generation...</div>;
if (!generation) return <div>Generation not found</div>;

return (
<div>
<div>Status: {generation.status}</div>
{generation.progress && <div>Progress: {generation.progress}%</div>}
{generation.output?.url && (
<img src={generation.output.url} alt={generation.prompt || ""} />
)}
</div>
);
}

useGenerators

Access available AI generators and their configurations:

import { useGenerators } from "@weirdfingers/boards";

function GeneratorSelector({ onSelect }) {
const { generators, isLoading } = useGenerators();

if (isLoading) return <div>Loading generators...</div>;

return (
<select onChange={(e) => onSelect(e.target.value)}>
<option value="">Select a generator</option>
{generators.map((generator) => (
<option key={generator.id} value={generator.id}>
{generator.name}
</option>
))}
</select>
);
}

Advanced Usage

Combining Hooks

Build complex UIs by combining multiple hooks:

import { useBoard, useGenerators } from "@weirdfingers/boards";

function BoardWithGenerators({ boardId }) {
const { board, isLoading: boardLoading } = useBoard(boardId);
const { generators, isLoading: generatorsLoading } = useGenerators();

if (boardLoading || generatorsLoading) return <div>Loading...</div>;

return (
<div>
<h2>{board.title}</h2>
<div className="generators">
{generators.map((generator) => (
<div key={generator.id}>
<h3>{generator.name}</h3>
<p>{generator.description}</p>
</div>
))}
</div>
</div>
);
}

TypeScript Support

The hooks are fully typed with TypeScript. Import types as needed:

import type {
Board,
Generation,
Provider,
GenerationRequest,
GenerationStatus,
} from "@weirdfingers/boards";

interface BoardCardProps {
board: Board;
onSelect: (board: Board) => void;
}

function BoardCard({ board, onSelect }: BoardCardProps) {
return (
<div onClick={() => onSelect(board)}>
<h3>{board.title}</h3>
<p>{board.description}</p>
<span>{board.generations?.length || 0} generations</span>
</div>
);
}

Error Handling

Handle errors gracefully with built-in error states:

import { useBoards } from "@weirdfingers/boards";

function BoardsWithErrorHandling() {
const { boards, error, retry, isLoading } = useBoards();

if (error) {
return (
<div className="error-state">
<h3>Failed to load boards</h3>
<p>{error.message}</p>
<button onClick={retry}>Try Again</button>
</div>
);
}

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div>
{boards.map((board) => (
<BoardCard key={board.id} board={board} />
))}
</div>
);
}

Next Steps