npm package: @genzzi/oauth-client
Description: Official Genzzi OAuth UI — customizable button + callback handler. Vanilla JS, React, Tailwind, any icon library.
Author: Genzzi
Latest version: 1.0.0

# @genzzi/oauth-client

Official Genzzi OAuth UI package. Customizable button + callback handler.  
Works with **Vanilla JS**, **React**, **Tailwind CSS**, and any icon library.  
Ships as **ESM + CJS** with full **TypeScript** types.

---

## Install

```bash
npm install @genzzi/oauth-client
```

React is an **optional** peer dependency — install it only if you use the React component.

```bash
npm install react react-dom        # only for React users
```

---

## How the flow works

```
1. User clicks the button
       ↓
2. Browser redirects to https://genzzi.in/oauth/oauthorize
   with  client_id, redirect_uri, scope, state
       ↓
3. User approves on Genzzi
       ↓
4. Genzzi redirects to YOUR redirect_uri
   with  ?code=ABC&state=XYZ  +  sets oauth_session cookie
       ↓
5. handleOAuthCallback() runs on that page
   - validates state (CSRF guard)
   - reads oauth_session cookie
   - POSTs { code, state, cookies } to YOUR backend
       ↓
6. Your backend exchanges the code → session → redirect to any page
```

The package **never contacts Genzzi from the callback**.  
Only step 2 (button click) talks to Genzzi. Everything else is your frontend ↔ your backend.

---

## React

### 1. Login page — the button

```tsx
import { GenzziButton } from '@genzzi/oauth-client';

export function LoginPage() {
  return (
    <GenzziButton
      clientId="YOUR_CLIENT_ID"
      redirectUri="https://myapp.com/callback"
      scopes={['email', 'profile']}
    />
  );
}
```

#### With Tailwind classes

```tsx
<GenzziButton
  clientId="YOUR_CLIENT_ID"
  redirectUri="https://myapp.com/callback"
  scopes={['email']}
  className="flex items-center gap-3 bg-zinc-900 text-white rounded-xl
             px-5 py-2.5 text-sm font-medium hover:bg-zinc-700 transition-colors"
  showLogo={false}
/>
```

> When you pass `className`, the built-in `variant` styles are skipped — Tailwind has full control.

#### With a custom icon (Lucide, Heroicons, Phosphor, etc.)

```tsx
import { Fingerprint } from 'lucide-react';

<GenzziButton
  clientId="YOUR_CLIENT_ID"
  redirectUri="https://myapp.com/callback"
  scopes={['email']}
  icon={<Fingerprint size={16} />}
  text="Verify with Genzzi"
  variant="light"
/>
```

#### With fully custom children

```tsx
<GenzziButton clientId="…" redirectUri="…" scopes={['email']}>
  <MyLogo className="w-5 h-5" />
  <span>Continue with Genzzi</span>
</GenzziButton>
```

#### Gate redirect behind agreement check

```tsx
<GenzziButton
  clientId="…" redirectUri="…" scopes={['email']}
  onBeforeRedirect={() => {
    if (!agreedToTerms) { setShowTerms(true); return false; }
    return true;
  }}
/>
```

#### All button props

| Prop | Type | Default | Description |
|---|---|---|---|
| `clientId` | `string` | required | Your Genzzi client_id |
| `redirectUri` | `string` | required | Your registered redirect URI |
| `scopes` | `OAuthScope[]` | required | `['email','profile','openid','apps']` |
| `text` | `string` | `"Sign in with Genzzi"` | Button label |
| `variant` | `'dark'\|'light'\|'outline'` | `'dark'` | Built-in style preset (ignored when `className` set) |
| `className` | `string` | — | Tailwind or custom CSS classes |
| `style` | `CSSProperties` | — | Inline style overrides |
| `icon` | `ReactNode` | — | Custom icon node (e.g. Lucide component) |
| `iconPosition` | `'left'\|'right'` | `'left'` | Which side the icon appears |
| `showLogo` | `boolean` | `true` | Show the Genzzi logo (auto-false when `icon` provided) |
| `disabled` | `boolean` | `false` | Disable the button |
| `onBeforeRedirect` | `() => boolean \| Promise<boolean>` | — | Return `false` to abort |
| `children` | `ReactNode` | — | Fully replaces built-in content |

---

### 2. Callback page — handle the return

```tsx
// app/callback/page.tsx  (Next.js App Router)
'use client';
import { useOAuthCallback } from '@genzzi/oauth-client';
import { useRouter } from 'next/navigation';

export default function CallbackPage() {
  const router = useRouter();

  const result = useOAuthCallback({
    backendCallbackUrl: '/api/auth/callback',   // YOUR backend route
    onSuccess: () => router.replace('/dashboard'),
    onError:   (err) => router.replace('/login?error=' + err.code),
  });

  if (!result) return <p>Authorizing…</p>;
  return null; // onSuccess/onError handle the redirect
}
```

#### Callback options

| Option | Type | Default | Description |
|---|---|---|---|
| `backendCallbackUrl` | `string` | required | Your own backend route |
| `method` | `'POST'\|'GET'` | `'POST'` | How to call your backend |
| `cookieNames` | `string[]` | `['oauth_session']` | Cookie names to forward |
| `onSuccess` | `(response) => void` | — | Called on 2xx — use for redirect |
| `onError` | `(error) => void` | — | Called on any failure |
| `cleanUrl` | `boolean` | `true` | Strip `code`/`state` from URL bar |

---

### 3. What your backend receives

```ts
// POST /api/auth/callback
// Content-Type: application/json
{
  code:    "authorization_code_from_genzzi",
  state:   "random_csrf_token",
  cookies: {
    oauth_session: "value_of_the_cookie"
  }
}
```

Use `@genzzi/oauth-server` to exchange this for tokens.

---

## Vanilla JS

### Login page

```js
import { mountGenzziButton } from '@genzzi/oauth-client/vanilla';

mountGenzziButton('#login-root', {
  clientId:    'YOUR_CLIENT_ID',
  redirectUri: 'https://myapp.com/callback',
  scopes:      ['email', 'profile'],
}, {
  text:    'Connect with Genzzi',
  variant: 'light',
});
```

#### With a custom SVG icon

```js
import { createGenzziButton } from '@genzzi/oauth-client/vanilla';

const btn = createGenzziButton(config, {
  text:     'Sign in securely',
  icon:     '<svg>…</svg>',    // any SVG or HTML string
  showLogo: false,
});
document.getElementById('root').appendChild(btn);
```

#### Fully custom HTML content

```js
const btn = createGenzziButton(config, {
  html: '<img src="/genzzi-logo.png" width="20" /> My custom button',
});
```

### Callback page

```js
import { handleOAuthCallback } from '@genzzi/oauth-client/callback';

handleOAuthCallback({
  backendCallbackUrl: '/api/auth/callback',
  onSuccess: (data) => {
    window.location.href = '/dashboard';
  },
  onError: (err) => {
    console.error(err.message);
    window.location.href = '/login?error=' + err.code;
  },
});
```

---

## Exports

| Import path | What you get |
|---|---|
| `@genzzi/oauth-client` | Everything (button + callback + utils + types) |
| `@genzzi/oauth-client/react` | `GenzziButton` + `useOAuthCallback` |
| `@genzzi/oauth-client/callback` | `handleOAuthCallback` + `useOAuthCallback` |
| `@genzzi/oauth-client/vanilla` | `createGenzziButton` + `mountGenzziButton` |

---

## Error codes

| Code | When |
|---|---|
| `MISSING_CODE` | No `code` param in the callback URL |
| `STATE_MISMATCH` | `state` doesn't match stored value (CSRF or expired session) |
| `NETWORK_ERROR` | Could not reach your backend |
| `SERVER_ERROR` | Your backend returned a non-2xx status |