# @micahgoodman/notes

A complete notes module SDK for React with backend API client, adapter system, and reusable UI components. **Can be used as a library, standalone app, or embedded with federated authentication.**

## ✨ Three Modes of Operation

### 📦 Library Mode (npm package)
Import into your React app as a module with full control over UI and auth.

### 🚀 Standalone Mode (self-contained app)
Deploy as a complete notes application with built-in authentication.

### 🔗 Embedded Mode (SDK + OAuth)
Import components and authenticate against this module's backend.

## Features

- **Complete API Client** - CRUD operations for notes with Supabase backend support
- **Module Adapter System** - Flexible adapter pattern for integrating notes into any app
- **React Components** - Ready-to-use UI components for list, whiteboard, and detail views
- **TypeScript** - Full type safety throughout
- **Realtime Updates** - Optional Supabase subscriptions for live data
- **Context-Aware** - Support for associating notes with other entities
- **Federated Auth** - Uses [@micahgoodman/auth](./auth) for Keycloak integration and multi-app SSO
- **Flexible Deployment** - Works as library, standalone app, or embedded widget

## Quick Start

Choose your mode:

### Library Mode (npm package)

```bash
npm install @micahgoodman/notes react @supabase/supabase-js
```

[→ Library Mode Guide](#library-mode-usage)

### Standalone Mode (complete app)

```bash
git clone https://github.com/micahgoodman/notes-module
cd notes-module
npm install
cp .env.example .env
# Edit .env with your credentials
npm run dev:standalone
```

[→ Standalone Setup Guide](./SETUP_GUIDE.md#setup-for-standalone-mode)

### Embedded Mode (SDK with remote backend)

```bash
# In your parent app
npm install @micahgoodman/notes @micahgoodman/auth keycloak-js

# Wrap components with RemoteAuthProvider from @micahgoodman/auth
```

[→ Embedded SDK Guide](./EMBEDDED_SDK_MODE.md)

---

## Library Mode Usage

### 1. Configure the API Client

```tsx
import { createClient } from '@supabase/supabase-js';
import { configureNotesApi, createNotesAdapter } from '@micahgoodman/notes';

const supabase = createClient(
  process.env.VITE_SUPABASE_URL,
  process.env.VITE_SUPABASE_ANON_KEY
);

// Configure the notes API
configureNotesApi({
  apiBase: process.env.VITE_API_BASE || '/api',
  supabaseClient: supabase,
  supabaseAnonKey: process.env.VITE_SUPABASE_ANON_KEY
});

// Create the notes adapter with realtime support
export const NoteAdapter = createNotesAdapter(supabase);
```

### 2. Use the Adapter Hook

```tsx
import { useModuleList } from '@micahgoodman/notes';
import { NoteAdapter } from './config';

function MyNotesComponent() {
  const { items: notes, loading, error, refresh } = useModuleList(NoteAdapter);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {notes.map(note => (
        <div key={note.id}>{note.text}</div>
      ))}
    </div>
  );
}
```

### 3. Use the UI Components

```tsx
import { NoteListItem, WhiteboardNoteCard, NoteDetailView } from '@micahgoodman/notes';

// In a list view
<NoteListItem
  item={note}
  isSelected={selectedId === note.id}
  onSelect={() => setSelectedId(note.id)}
  index={0}
/>

// On a whiteboard
<WhiteboardNoteCard
  note={note}
  isSelected={selectedId === note.id}
  onClick={() => setSelectedId(note.id)}
  onCardClick={handleCardClick}
/>

// Detail view
<NoteDetailView
  note={selectedNote}
  onUpdated={refresh}
  onDeleted={() => { setSelectedNote(null); refresh(); }}
  onShowToast={(msg) => console.log(msg)}
/>
```

## API Reference

### Configuration

#### `configureNotesApi(config: ApiConfig)`

Configure the notes API client before using any API functions.

```tsx
configureNotesApi({
  apiBase: '/api',                    // Your API base URL
  supabaseClient: supabaseClient,     // Optional: for auth headers
  supabaseAnonKey: 'your-anon-key'    // Optional: for auth headers
});
```

### API Functions

#### `fetchNotes(): Promise<Note[]>`
Fetch all notes.

#### `fetchNotesByContext(contextType: string, contextId: string): Promise<Note[]>`
Fetch notes associated with a specific context.

#### `createNote(input: CreateNoteInput): Promise<{ id: string }>`
Create a new note.

#### `updateNote(id: string, input: UpdateNoteInput): Promise<{ ok: boolean }>`
Update an existing note.

#### `deleteNote(id: string): Promise<{ ok: boolean }>`
Delete a note.

### Adapter System

#### `createNotesAdapter(supabaseClient?: SupabaseClient)`

Create a notes adapter instance with optional realtime subscriptions.

```tsx
const NoteAdapter = createNotesAdapter(supabase);
```

#### `useModuleList(adapter: ModuleAdapter, opts?: { context?: Context })`

React hook for managing a list of items with the adapter.

```tsx
// All notes
const { items, loading, error, refresh } = useModuleList(NoteAdapter);

// Notes for a specific context
const { items } = useModuleList(NoteAdapter, {
  context: { type: 'project', id: '123' }
});
```

### Components

#### `<NoteListItem />`
Renders a note in a list view (for use with `@micahgoodman/sidebar` or custom lists).

**Props:**
- `item: Note` - The note to display
- `isSelected: boolean` - Whether the note is selected
- `onSelect: () => void` - Callback when note is clicked
- `index: number` - Index in the list

#### `<WhiteboardNoteCard />`
Renders a note as a card (for use with `@micahgoodman/whiteboard` or custom canvases).

**Props:**
- `note: Note` - The note to display
- `isSelected: boolean` - Whether the note is selected
- `onClick: () => void` - Callback when card content is clicked
- `onCardClick: (e: React.MouseEvent) => void` - Callback for card wrapper clicks

#### `<NoteDetailView />`
Renders a detailed view/editor for a note.

**Props:**
- `note: Note | null` - The note to display
- `onUpdated: () => void` - Callback after successful update
- `onDeleted: () => void` - Callback after successful deletion
- `onShowToast: (message: string) => void` - Callback for showing notifications
- `contextChain?: any[]` - Optional context chain for embedded display
- `hideEmbedded?: boolean` - Whether to hide embedded content

#### `<CreateNoteModal />`
Modal for creating a new note.

**Props:**
- `onClose: () => void` - Callback to close the modal
- `onCreate: () => void` - Callback after successful creation
- `context?: Context` - Optional context to associate the note with

### Types

```tsx
type Note = {
  id: string;
  text: string;
  createdAt: string;
  updatedAt: string;
};

type Context = {
  type: string;
  id: string;
};

type CreateNoteInput = {
  text: string;
  context?: Context;
};

type UpdateNoteInput = {
  text?: string;
};
```

## Complete Example: Notes App

```tsx
import React, { useState } from 'react';
import { createClient } from '@supabase/supabase-js';
import {
  configureNotesApi,
  createNotesAdapter,
  useModuleList,
  NoteListItem,
  NoteDetailView,
  CreateNoteModal,
} from '@micahgoodman/notes';

// Configure
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
configureNotesApi({
  apiBase: '/api',
  supabaseClient: supabase,
  supabaseAnonKey: SUPABASE_KEY
});
const NoteAdapter = createNotesAdapter(supabase);

// App
function NotesApp() {
  const { items: notes, loading, error, refresh } = useModuleList(NoteAdapter);
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [showCreate, setShowCreate] = useState(false);
  
  const selectedNote = notes.find(n => n.id === selectedId) || null;

  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      {/* Sidebar */}
      <div style={{ width: '300px', borderRight: '1px solid #ccc', overflowY: 'auto' }}>
        <button onClick={() => setShowCreate(true)}>+ New Note</button>
        {notes.map((note, index) => (
          <NoteListItem
            key={note.id}
            item={note}
            isSelected={selectedId === note.id}
            onSelect={() => setSelectedId(note.id)}
            index={index}
          />
        ))}
      </div>

      {/* Detail */}
      <div style={{ flex: 1, overflowY: 'auto' }}>
        {selectedNote && (
          <NoteDetailView
            note={selectedNote}
            onUpdated={refresh}
            onDeleted={() => { setSelectedId(null); refresh(); }}
            onShowToast={(msg) => alert(msg)}
          />
        )}
      </div>

      {/* Create Modal */}
      {showCreate && (
        <CreateNoteModal
          onClose={() => setShowCreate(false)}
          onCreate={() => { setShowCreate(false); refresh(); }}
        />
      )}
    </div>
  );
}
```

## Integration with Other Packages

This package is designed to work seamlessly with:

- **@micahgoodman/whiteboard** - For whiteboard/canvas layouts
- **@micahgoodman/sidebar** - For sidebar/list layouts

Example with both:

```tsx
import { Whiteboard } from '@micahgoodman/whiteboard';
import { SidebarLayout, SidebarList } from '@micahgoodman/sidebar';
import { NoteListItem, WhiteboardNoteCard, NoteDetailView } from '@micahgoodman/notes';

// List View
<SidebarLayout
  SidebarComponent={
    <SidebarList
      items={notes}
      ItemComponent={NoteListItem}
      // ... other props
    />
  }
  DetailComponent={<NoteDetailView note={selectedNote} />}
/>

// Whiteboard View
<Whiteboard
  items={notes.map(n => ({ ...n, type: 'note' }))}
  componentMap={{
    note: {
      CardComponent: (props) => <WhiteboardNoteCard note={props.item} ... />,
      DetailComponent: (props) => <NoteDetailView note={props.item} ... />
    }
  }}
/>
```

## Backend Requirements

This package includes a complete Supabase backend:

- **Database schema** - `module_data` table with RLS policies
- **Edge Functions** - `/functions/v1/concepts` API endpoint
- **Migrations** - In `supabase/migrations/`
- **Realtime** - Optional subscriptions for live updates

### API Endpoints

- `GET /concepts?filter=note` - List all notes
- `GET /concepts?filter=note&contextType=X&contextId=Y` - List notes by context
- `POST /concepts?filter=note` - Create note
- `PATCH /concepts/:id` - Update note
- `DELETE /concepts/:id` - Delete note

## Authentication

### Library Mode
Consumer provides authentication (any auth system).

### Standalone Mode
Built-in Supabase Auth with Keycloak OAuth provider.

### Embedded SDK Mode
Parent app handles Keycloak OAuth, components authenticate silently against remote backend. User logs in once.

[→ Full Auth Documentation](./ARCHITECTURE.md#authentication-strategy)

## Documentation

- **[GETTING_STARTED.md](./GETTING_STARTED.md)** - Quick start guide
- **[EMBEDDED_SDK_MODE.md](./EMBEDDED_SDK_MODE.md)** - ⭐ Recommended embedded approach
- **[SINGLE_SIGN_ON.md](./SINGLE_SIGN_ON.md)** - How SSO works (login once, use everywhere)
- **[MODE_COMPARISON.md](./MODE_COMPARISON.md)** - Help choosing the right mode
- **[SETUP_GUIDE.md](./SETUP_GUIDE.md)** - Complete setup for all modes
- **[USAGE_GUIDE.md](./USAGE_GUIDE.md)** - Detailed usage examples
- **[ARCHITECTURE.md](./ARCHITECTURE.md)** - System design and architecture
- **[KEYCLOAK_INTEGRATION.md](./KEYCLOAK_INTEGRATION.md)** - Keycloak federation guide

## Project Structure

```
notes-module/
├── src/
│   ├── api.ts              # API client
│   ├── adapter.ts          # Notes adapter
│   ├── types.ts            # TypeScript types
│   ├── auth/               # Authentication (uses @micahgoodman/auth)
│   │   ├── AuthProvider.tsx  # Mode-based auth wrapper
│   │   ├── types.ts          # AppMode type
│   │   ├── utils.ts          # Mode detection utils
│   │   └── index.ts          # Re-exports from @micahgoodman/auth
│   ├── standalone/         # Standalone app
│   │   ├── App.tsx
│   │   └── pages/
│   └── components/         # Shared UI components
├── auth/                   # @micahgoodman/auth package (local)
│   ├── src/                # Auth provider implementations
│   └── README.md           # Auth package documentation
├── supabase/
│   ├── migrations/         # Database migrations
│   └── functions/          # Edge functions
├── standalone.html         # Standalone entry point
└── SINGLE_SIGN_ON.md      # SSO explanation
```

## Contributing

Contributions welcome! Please read our contributing guidelines.

## License

MIT
