# @benbraide/auth-service-nextjs

Next.js/React component library for integrating iframe-based account switcher with authentication service.

## Features

✅ **Full TypeScript Support** - Complete type definitions for all components and hooks
✅ **SSR Compatible** - Works seamlessly with Next.js server-side rendering
✅ **Interactive Dialogs** - Built-in support for confirm, prompt, and alert dialogs
✅ **Auto-Resize** - Iframe automatically adjusts height based on content
✅ **Tree-Shakeable** - Modern ESM and CJS builds with optimized bundle size
✅ **Zero Dependencies** - Only peer dependencies on React 18+
✅ **Cross-Product Handoff** - `@benbraide/auth-service-nextjs/sharing` ships the destination-side `/auth/handoff` route handler, landing page, multi-account auto-add primitive, and arrival toast. See [docs/sharing.md](docs/sharing.md).

## Installation

```bash
npm install @benbraide/auth-service-nextjs
```

## Quick Start

### 1. Wrap your app with `AccountSwitcherProvider`

```typescript
// app/layout.tsx
import { AccountSwitcherProvider } from '@benbraide/auth-service-nextjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AccountSwitcherProvider
          backendUrl={process.env.NEXT_PUBLIC_API_URL!}
          authServiceUrl="https://auth.yourdomain.com"
          onSessionChange={(session) => {
            console.log('Session changed:', session);
          }}
        >
          {children}
        </AccountSwitcherProvider>
      </body>
    </html>
  );
}
```

### 2. Use the `AccountSwitcher` component

```typescript
// components/Header.tsx
'use client';

import { AccountSwitcher, useSession } from '@benbraide/auth-service-nextjs';

export function Header() {
  const { currentUser, isAuthenticated } = useSession();

  return (
    <header className="flex items-center justify-between p-4">
      <h1>My App</h1>

      {isAuthenticated && (
        <div className="flex items-center gap-4">
          <span>Welcome, {currentUser?.name}!</span>
          <AccountSwitcher />
        </div>
      )}
    </header>
  );
}
```

### 3. Create API route for token generation

The package expects a `/api/account-switcher/token` endpoint that returns the widget token:

```typescript
// app/api/account-switcher/token/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  try {
    // Call your backend (Laravel/Node/etc.) to get the widget token
    const response = await fetch(`${process.env.BACKEND_URL}/api/account-switcher/token`, {
      headers: {
        'Accept': 'application/json',
      },
      credentials: 'include',
    });

    const data = await response.json();

    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch token' },
      { status: 500 }
    );
  }
}
```

## Components

### `AccountSwitcherProvider`

Context provider that manages session state and iframe communication.

**Props:**

```typescript
interface AccountSwitcherConfig {
  backendUrl: string;                              // Required: Your backend API URL
  authServiceUrl?: string;                         // Optional: Auth service URL
  onSessionChange?: (session: Session) => void;    // Optional: Session change callback
  onAccountChange?: (user: User) => void;          // Optional: Account change callback
  onError?: (error: Error) => void;                // Optional: Error callback
  debug?: boolean;                                 // Optional: Enable debug logging

  // Dialog configuration
  dialogs?: {
    enabled?: boolean;                             // Default: true
    closeOnOverlayClick?: boolean;                 // Default: true
    closeOnEscape?: boolean;                       // Default: true
  };

  // Custom dialog handlers
  onConfirmRequest?: (payload: ConfirmPayload) => Promise<boolean>;
  onPromptRequest?: (payload: PromptPayload) => Promise<string | null>;
  onAlertRequest?: (payload: AlertPayload) => Promise<void>;

  // Auto-resize configuration
  autoResize?: boolean;                            // Default: true
  minHeight?: number;                              // Default: 200
  maxHeight?: number | null;                       // Default: null (unlimited)
  onResize?: (height: number) => void;             // Optional: Resize callback

  // Navigation handlers (optional - default: window.location.href)
  onNavigateToAddAccount?: (url: string) => void;     // Handle add account navigation
  onNavigateToManageAccount?: (url: string) => void;  // Handle account management navigation
  onNavigateToAvatarUpdate?: (url: string) => void;   // Handle avatar update navigation
}
```

### `AccountAvatar`

Compact, clickable avatar component for NAV bars that displays the current user's avatar and toggles the account switcher visibility.

**Props:**

```typescript
interface AccountAvatarProps {
  size?: number;                                  // Avatar diameter in pixels (default: 40)
  className?: string;                             // Additional CSS classes
  style?: React.CSSProperties;                    // Custom inline styles
  targetId?: string;                              // ID of element to toggle (IFRAME container)
  onClick?: () => void;                           // Custom click handler
  onAvatarClick?: (user: User | null) => void;   // Callback with user data on click
}
```

**Features:**
- Displays user avatar (image or initials) when logged in
- Shows generic profile icon when logged out
- Automatically syncs with session changes
- Toggles target element visibility (typically IFRAME container)
- Outside click and ESC key support for closing popup
- Smooth hover and active animations

**Example:**

```typescript
// In your NAV component
'use client';

import { AccountAvatar } from '@benbraide/auth-service-nextjs';

export function Navbar() {
  return (
    <nav className="navbar">
      <div className="logo">My App</div>

      <div className="nav-items">
        {/* Avatar that toggles the dropdown */}
        <AccountAvatar
          size={40}
          targetId="account-switcher-dropdown"
          onAvatarClick={(user) => console.log('Clicked:', user?.name)}
        />
      </div>
    </nav>

    {/* Hidden dropdown with AccountSwitcher */}
    <div
      id="account-switcher-dropdown"
      style={{
        display: 'none',
        position: 'absolute',
        top: '60px',
        right: '20px',
        zIndex: 1000,
      }}
    >
      <AccountSwitcher width={320} height={400} />
    </div>
  );
}
```

### `AccountSwitcher`

Iframe component that displays the account switcher widget.

**Props:**

```typescript
interface AccountSwitcherProps {
  className?: string;                             // Optional: CSS class
  position?: 'top-left' | 'top-right' |          // Optional: Position
             'bottom-left' | 'bottom-right';
  width?: number;                                // Optional: Width in pixels
  height?: number;                               // Optional: Height in pixels
}
```

## Hooks

### `useSession()`

Returns current session state.

```typescript
const {
  isAuthenticated,
  currentUser,
  accounts,
  isLoading,
  error
} = useSession();
```

### `useUser()`

Returns current user object.

```typescript
const user = useUser();
```

### `useAccountSwitcher()`

Returns account switcher methods.

```typescript
const {
  switchAccount,
  addAccount,
  manageAccount,
  updateAvatar,
  logoutAccount,
  logoutAll,
  refresh
} = useAccountSwitcher();

// Switch to a different account
await switchAccount('user-uuid-here');

// Add new account
await addAccount();

// Navigate to account management page
await manageAccount();

// Navigate to avatar update page
await updateAvatar();

// Logout specific account
await logoutAccount('user-uuid');

// Logout all accounts
await logoutAll();

// Refresh session
await refresh();
```

## Advanced Usage

### Custom Dialog Handlers

Use your own UI library (e.g., SweetAlert2, Radix UI):

```typescript
<AccountSwitcherProvider
  backendUrl={process.env.NEXT_PUBLIC_API_URL!}
  onConfirmRequest={async (payload) => {
    const result = await Swal.fire({
      title: payload.title,
      text: payload.message,
      icon: payload.variant === 'danger' ? 'warning' : 'question',
      showCancelButton: true,
      confirmButtonText: payload.confirmText || 'OK',
    });
    return result.isConfirmed;
  }}
>
  {children}
</AccountSwitcherProvider>
```

### Event Handlers

```typescript
<AccountSwitcherProvider
  backendUrl={process.env.NEXT_PUBLIC_API_URL!}
  onSessionChange={(session) => {
    if (session.isAuthenticated) {
      // Load user data
      fetchUserData(session.currentUser.uuid);
    }
  }}
  onAccountChange={(user) => {
    // Reload page or refresh data
    window.location.reload();
  }}
  onError={(error) => {
    toast.error(error.message);
  }}
>
  {children}
</AccountSwitcherProvider>
```

### Custom Navigation Handlers

Override default navigation behavior for account flows:

```typescript
<AccountSwitcherProvider
  backendUrl={process.env.NEXT_PUBLIC_API_URL!}
  onNavigateToAddAccount={(url) => {
    // Use Next.js router instead of window.location
    router.push(url);
  }}
  onNavigateToManageAccount={(url) => {
    // Open in new tab
    window.open(url, '_blank');
  }}
  onNavigateToAvatarUpdate={(url) => {
    // Custom modal implementation
    openAvatarModal(url);
  }}
>
  {children}
</AccountSwitcherProvider>
```

### Debug Mode

Enable verbose logging:

```typescript
<AccountSwitcherProvider
  backendUrl={process.env.NEXT_PUBLIC_API_URL!}
  debug={process.env.NODE_ENV === 'development'}
>
  {children}
</AccountSwitcherProvider>
```

## TypeScript Types

```typescript
interface User {
  uuid: string;
  name: string;
  email: string;
  avatar_url?: string;
  created_at?: string;
}

interface Session {
  isAuthenticated: boolean;
  currentUser: User | null;
  accounts: User[];
}
```

## Backend Integration

Your backend should implement these endpoints:

### `GET /api/account-switcher/token`

Generate widget embed token.

**Response:**
```json
{
  "token": "eyJ...",
  "embed_url": "https://auth.example.com/widgets/embed?token=eyJ...",
  "expires_at": "2025-10-02 01:30:00"
}
```

### `POST /api/account-switcher/sync-session` (Optional)

Sync session after account switch.

**Request:**
```json
{
  "auth_token": "eyJ...",
  "user_uuid": "user-uuid"
}
```

## License

MIT

## Author

Ben Braide
