# LiveSheets SDK

Official SDK for integrating Google Sheets with Lovable applications. Provides OAuth authentication, sheet selection, and data access with built-in React hooks support.

## Installation

```bash
npm install livesheets-sdk
```

## Quick Start

### Using React Hooks (Recommended for Lovable Apps)

```tsx
import { useLiveSheets } from 'livesheets-sdk/react';

function GoogleSheetsConnector() {
  const {
    isAuthenticated,
    hasSheet,
    data,
    isLoading,
    error,
    connect,
    selectSheet,
    disconnect,
    refresh
  } = useLiveSheets();

  if (!isAuthenticated) {
    return <button onClick={connect}>Connect Google Sheets</button>;
  }

  if (!hasSheet) {
    return <button onClick={selectSheet}>Select a Sheet</button>;
  }

  return (
    <div>
      <h3>Sheet Data ({data.length} rows)</h3>
      <button onClick={refresh}>Refresh</button>
      <button onClick={disconnect}>Disconnect</button>
      {/* Render your data */}
    </div>
  );
}
```

### Using the Core SDK

```typescript
import { LiveSheetsClient, LiveSheetsAuth } from 'livesheets-sdk';

// Initialize auth
const auth = new LiveSheetsAuth();

// Check if authenticated
if (!auth.isAuthenticated()) {
  // Start OAuth flow
  auth.startOAuth();
}

// Get JWT token
const jwt = auth.getJWT();

// Create client and fetch data
const client = new LiveSheetsClient(jwt);
const rows = await client.listRows();
const metadata = await client.getMetadata();
const schema = await client.getSchema();
```

## Features

### 🔐 OAuth Authentication

Built-in OAuth flow management with automatic JWT handling:

```typescript
import { auth } from 'livesheets-sdk';

// Start OAuth flow (redirects to Google)
auth.startOAuth();

// Check authentication status
if (auth.isAuthenticated()) {
  const jwt = auth.getJWT();
  // Use JWT for API calls
}

// Logout
auth.logout();
```

### 📊 Sheet Selection

Interactive Google Picker integration:

```typescript
import { LiveSheetsClient } from 'livesheets-sdk';

await LiveSheetsClient.openSheetPicker({
  jwt: 'your-jwt-token',
  onSuccess: (result) => {
    console.log('Selected:', result.spreadsheetId, result.sheetName);
  },
  onCancel: () => {
    console.log('Selection cancelled');
  }
});
```

### 🎣 React Hooks

Complete hook with all features:

```typescript
const {
  // Auth state
  isAuthenticated,    // boolean
  isConnecting,       // boolean
  
  // Sheet state  
  hasSheet,          // boolean
  isSelectingSheet,  // boolean
  sheetMetadata,     // { spreadsheetId, sheetName, rowCount, columnCount }
  
  // Data state
  data,              // SheetRow[]
  isLoading,         // boolean
  error,             // Error | null
  
  // Actions
  connect,           // () => void
  disconnect,        // () => void
  selectSheet,       // () => Promise<void>
  refresh,           // () => Promise<void>
  
  // Direct access
  client             // LiveSheetsClient | null
} = useLiveSheets(options);
```

Simple data-only hook:

```typescript
const { data, loading, error } = useSheetData(jwt);
```

## API Reference

### LiveSheetsAuth

```typescript
class LiveSheetsAuth {
  constructor(config?: {
    backendUrl?: string;
    storageKey?: string;
    userIdKey?: string;
  })
  
  startOAuth(returnUrl?: string): void
  getJWT(): string | null
  isAuthenticated(): boolean
  logout(): void
}
```

### LiveSheetsClient

```typescript
class LiveSheetsClient {
  constructor(jwt?: string, baseUrl?: string)
  
  // Data methods
  async listRows(): Promise<SheetRow[]>
  async getMetadata(): Promise<SheetMetadata>
  async getSchema(): Promise<SheetSchema[]>
  
  // Static methods
  static getAuth(config?: AuthConfig): LiveSheetsAuth
  static async openSheetPicker(config: SheetPickerConfig): Promise<void>
}
```

### Types

```typescript
interface SheetRow {
  [key: string]: any;
}

interface SheetMetadata {
  spreadsheetId: string;
  sheetName: string;
  rowCount: number;
  columnCount: number;
}

interface SheetSchema {
  name: string;
  type: string;
}
```

## Complete Example

```tsx
import React from 'react';
import { useLiveSheets } from 'livesheets-sdk/react';

export default function DataTable() {
  const {
    isAuthenticated,
    hasSheet,
    data,
    isLoading,
    error,
    connect,
    selectSheet,
    refresh,
    sheetMetadata
  } = useLiveSheets({
    autoLoadData: true,
    onDataLoaded: (rows) => {
      console.log(`Loaded ${rows.length} rows`);
    }
  });

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

  if (!isAuthenticated) {
    return (
      <button onClick={connect} disabled={isLoading}>
        {isLoading ? 'Connecting...' : 'Connect Google Sheets'}
      </button>
    );
  }

  if (!hasSheet) {
    return (
      <button onClick={selectSheet} disabled={isLoading}>
        {isLoading ? 'Opening picker...' : 'Select a Sheet'}
      </button>
    );
  }

  return (
    <div>
      <h2>{sheetMetadata?.sheetName}</h2>
      <p>{data.length} rows × {sheetMetadata?.columnCount} columns</p>
      
      <button onClick={refresh} disabled={isLoading}>
        {isLoading ? 'Loading...' : 'Refresh'}
      </button>
      
      <table>
        <thead>
          <tr>
            {data[0] && Object.keys(data[0]).map(key => (
              <th key={key}>{key}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {data.map((row, i) => (
            <tr key={i}>
              {Object.values(row).map((val, j) => (
                <td key={j}>{String(val)}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
```

## Lovable Integration Instructions

### Simple Component for Lovable

Here's a complete GoogleSheetsConnector component that works perfectly with Lovable:

```tsx
import React from 'react';
import { useLiveSheets } from 'livesheets-sdk/react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Loader2 } from 'lucide-react';

export default function GoogleSheetsConnector() {
  const {
    isAuthenticated,
    hasSheet,
    data,
    isLoading,
    error,
    connect,
    selectSheet,
    disconnect,
    refresh,
    sheetMetadata
  } = useLiveSheets();

  if (!isAuthenticated) {
    return (
      <Card>
        <CardHeader>
          <CardTitle>Connect Google Sheets</CardTitle>
        </CardHeader>
        <CardContent className="text-center">
          <Button onClick={connect} disabled={isLoading}>
            {isLoading ? (
              <>
                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                Connecting...
              </>
            ) : (
              'Connect Google Sheets'
            )}
          </Button>
        </CardContent>
      </Card>
    );
  }

  if (!hasSheet) {
    return (
      <Card>
        <CardHeader>
          <CardTitle>Select a Spreadsheet</CardTitle>
        </CardHeader>
        <CardContent className="text-center space-y-4">
          <Button onClick={selectSheet} disabled={isLoading}>
            {isLoading ? (
              <>
                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                Opening picker...
              </>
            ) : (
              'Select Sheet'
            )}
          </Button>
          <Button variant="ghost" onClick={disconnect}>
            Disconnect
          </Button>
        </CardContent>
      </Card>
    );
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>{sheetMetadata?.sheetName || 'Sheet Data'}</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="mb-4 flex gap-2">
          <Button onClick={refresh} disabled={isLoading} size="sm">
            {isLoading ? (
              <>
                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                Loading...
              </>
            ) : (
              'Refresh'
            )}
          </Button>
          <Button onClick={selectSheet} variant="outline" size="sm">
            Change Sheet
          </Button>
          <Button onClick={disconnect} variant="ghost" size="sm">
            Disconnect
          </Button>
        </div>
        
        {error && (
          <div className="text-red-500 mb-4">{error.message}</div>
        )}
        
        {data.length > 0 && (
          <div className="overflow-x-auto">
            <table className="w-full border">
              <thead>
                <tr>
                  {Object.keys(data[0]).map(key => (
                    <th key={key} className="border p-2 text-left">
                      {key}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {data.slice(0, 10).map((row, i) => (
                  <tr key={i}>
                    {Object.values(row).map((val, j) => (
                      <td key={j} className="border p-2">
                        {String(val)}
                      </td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
            {data.length > 10 && (
              <p className="text-center mt-2 text-sm text-muted-foreground">
                Showing 10 of {data.length} rows
              </p>
            )}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
```

### Integration Steps for Lovable

1. **Install the SDK**: Add `livesheets-sdk` to your dependencies
2. **Copy the component above** into your project
3. **Add it to your page**: Just drop `<GoogleSheetsConnector />` anywhere
4. **That's it!** The component handles everything else

## Configuration

### Backend URL

Default: `https://livesheets-backend-624058308714.europe-north1.run.app`

To use a custom backend:

```typescript
const auth = new LiveSheetsAuth({
  backendUrl: 'https://your-backend-url.com'
});

// Or with hooks
const { connect } = useLiveSheets({
  backendUrl: 'https://your-backend-url.com'
});
```

### Storage Keys

Customize localStorage keys:

```typescript
const auth = new LiveSheetsAuth({
  storageKey: 'my_app_jwt',
  userIdKey: 'my_app_user_id'
});
```

## OAuth Setup

The SDK handles the complete OAuth flow:

1. User clicks "Connect" → Redirects to Google OAuth
2. User authorizes → Redirects back with JWT
3. SDK stores JWT in localStorage
4. All subsequent API calls use the JWT

### Required Google OAuth Scopes

- `https://www.googleapis.com/auth/spreadsheets.readonly`
- `https://www.googleapis.com/auth/drive.readonly`

### OAuth Onboarding URL

For manual integration or testing:
```
https://livesheets-backend-624058308714.europe-north1.run.app/onboarding
```

## Alternative Usage: readSheet Helper

For simple use cases, you can use the `readSheet` helper:

```javascript
import { readSheet } from 'livesheets-sdk';

// Option 1: Pass JWT directly
const rows = await readSheet(jwt);

// Option 2: Auto-detect JWT from localStorage
const rows = await readSheet();

// Option 3: Pass options
const rows = await readSheet({ 
  jwt: jwt,           // Optional if stored in localStorage
  sheetId: 'abc123',  // For future use
  sheet: 'Sheet1'     // For future use
});
```

## Common Issues

### Import Errors

Always use named imports with the `new` keyword:

```typescript
// ✅ CORRECT
import { LiveSheetsClient } from 'livesheets-sdk';
const client = new LiveSheetsClient(jwt);

// ❌ WRONG - Missing 'new' keyword
import { LiveSheetsClient } from 'livesheets-sdk';
const client = LiveSheetsClient(jwt); // Error!
```

### TypeScript Errors

When rendering data, convert to strings:

```tsx
// ✅ CORRECT
<td>{String(cell)}</td>

// ❌ WRONG - Type error
<td>{cell}</td>
```

## Browser Support

- Chrome, Firefox, Safari, Edge (latest versions)
- Requires localStorage and postMessage support
- Popup blockers must allow the sheet picker

## License

MIT