npm package: @humanity-org/react-sdk
Description: React components and hooks for Humanity Protocol OAuth and verification
Author: Humanity Protocol
Latest version: 0.0.3

<p align="center">
  <img src="https://cdn.humanity.org/humanity-protocol-logo-devs.png" width="576" alt="Humanity Protocol">
</p>

<h1 align="center">@humanity-org/react-sdk</h1>

<p align="center">
  Official React SDK for Humanity Protocol OAuth and verification. Reduce integration time from 2–5 days to ~30 minutes with ready-made components.
</p>

<p align="center">
  <a href="https://docs.humanity.org"><img src="https://img.shields.io/badge/docs-humanity.org-blue.svg" alt="Documentation"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
  <a href="https://discord.gg/humanity"><img src="https://img.shields.io/badge/discord-join-7289da.svg" alt="Discord"></a>
</p>

<div align="center">

[![npm](https://img.shields.io/npm/v/@humanity-org/react-sdk)](https://www.npmjs.com/package/@humanity-org/react-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/Node-18+-purple.svg)](https://nodejs.org)

</div>

---

## Table of Contents

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Components](#components)
   - [HumanityProvider](#humanityprovider)
   - [HumanityConnect](#humanityconnect)
   - [HumanityVerify](#humanityverify)
   - [HumanityGate](#humanitygate)
   - [HumanityProfile](#humanityprofile)
   - [HumanityErrorBoundary](#humanityerrorboundary)
4. [Hooks](#hooks)
   - [useHumanity](#usehumanity)
   - [useAuth](#useauth)
   - [useVerification](#useverification)
   - [usePresets](#usepresets)
   - [useCredentialUpdates](#usecredentialupdates)
5. [TypeScript Types](#typescript-types)
6. [Error Handling](#error-handling)
7. [Testing](#testing)
8. [Theming](#theming)
9. [Production Checklist](#production-checklist)
10. [License](#license)

---

## Installation

```bash
npm install @humanity-org/react-sdk
# or
yarn add @humanity-org/react-sdk
# or
pnpm add @humanity-org/react-sdk
```

---

## Quick Start

### 1. Wrap your app with `HumanityProvider`

```tsx
import { HumanityProvider } from '@humanity-org/react-sdk'

function App() {
  return (
    <HumanityProvider
      clientId="hp_xxx"
      redirectUri="https://yourapp.com/callback"
      environment="production"
    >
      <YourApp />
    </HumanityProvider>
  )
}
```

### 2. Add a login button

```tsx
import { HumanityConnect } from '@humanity-org/react-sdk'

function LoginPage() {
  return (
    <HumanityConnect
      scopes={['openid', 'profile', 'identity:read']}
      onSuccess={(result) => console.log('Logged in!', result)}
    />
  )
}
```

### 3. Access user data and gate content

```tsx
import { useAuth, HumanityGate } from '@humanity-org/react-sdk'

function Dashboard() {
  const { user, isAuthenticated, logout } = useAuth()

  if (!isAuthenticated) return <div>Please log in</div>

  return (
    <div>
      <h1>Welcome, {user?.name}!</h1>
      <HumanityGate
        preset="ageOver21"
        fallback={<p>Age verification required</p>}
      >
        <PremiumContent />
      </HumanityGate>
      <button onClick={logout}>Sign Out</button>
    </div>
  )
}
```

---

## Components

All components must be rendered inside a `<HumanityProvider>`.

### `HumanityProvider`

The root context provider. Wraps your entire application.

```tsx
<HumanityProvider
  clientId="hp_xxx"
  redirectUri="https://app.com/callback"
  environment="production"
  storage="memory"
  theme="system"
  onError={(err) => console.error(err)}
>
  <App />
</HumanityProvider>
```

**Props**

| Prop          | Type                                             | Default        | Description                                                                  |
| ------------- | ------------------------------------------------ | -------------- | ---------------------------------------------------------------------------- |
| `clientId`    | `string`                                         | **required**   | Your Humanity application client ID.                                         |
| `redirectUri` | `string`                                         | **required**   | OAuth callback URI registered in the developer portal.                       |
| `environment` | `'production' \| 'sandbox'`                      | `'production'` | Which Humanity API environment to use.                                       |
| `storage`     | `'memory' \| 'localStorage' \| 'sessionStorage'` | `'memory'`     | Token storage strategy. `memory` is most secure (tokens cleared on refresh). |
| `theme`       | `'light' \| 'dark' \| 'system'`                  | `'system'`     | Controls built-in component theming.                                         |
| `baseUrl`     | `string`                                         | —              | Advanced override for custom API base URL.                                   |
| `onError`     | `(error: HumanityReactError) => void`            | —              | Global error handler called for auth/verification errors.                    |

---

### `HumanityConnect`

OAuth login button that handles the full authentication flow.

```tsx
<HumanityConnect
  scopes={['openid', 'profile', 'identity:read']}
  mode="popup"
  variant="primary"
  size="md"
  label="Sign in with Humanity"
  onSuccess={(result) => handleLogin(result)}
  onError={(error) => console.error(error)}
/>
```

**Props**

| Prop        | Type                                    | Default                   | Description                                        |
| ----------- | --------------------------------------- | ------------------------- | -------------------------------------------------- |
| `scopes`    | `string[]`                              | `['openid', 'profile']`   | OAuth scopes to request.                           |
| `mode`      | `'popup' \| 'redirect'`                 | `'popup'`                 | OAuth flow type.                                   |
| `onSuccess` | `(result: AuthResult) => void`          | —                         | Called after successful authentication.            |
| `onError`   | `(error: HumanityReactError) => void`   | —                         | Called on authentication error.                    |
| `onLoading` | `(loading: boolean) => void`            | —                         | Called when loading state changes.                 |
| `variant`   | `'primary' \| 'secondary' \| 'outline'` | `'primary'`               | Button visual variant.                             |
| `size`      | `'sm' \| 'md' \| 'lg'`                  | `'md'`                    | Button size.                                       |
| `label`     | `string`                                | `'Sign in with Humanity'` | Button label text.                                 |
| `disabled`  | `boolean`                               | `false`                   | Disable the button.                                |
| `className` | `string`                                | —                         | Additional CSS class.                              |
| `style`     | `React.CSSProperties`                   | —                         | Inline styles.                                     |
| `children`  | `React.ReactNode`                       | —                         | Custom children (replaces default button content). |

---

### `HumanityVerify`

One-click verification against a single preset.

```tsx
<HumanityVerify
  preset="ageOver21"
  onVerified={(result) => {
    if (result.verified) grantAccess()
  }}
/>
```

**Props**

| Prop         | Type                                    | Default      | Description                                  |
| ------------ | --------------------------------------- | ------------ | -------------------------------------------- |
| `preset`     | `string`                                | **required** | Preset key to verify (e.g. `'ageOver21'`).   |
| `onVerified` | `(result: VerificationResult) => void`  | —            | Called after verification completes.         |
| `onError`    | `(error: HumanityReactError) => void`   | —            | Called on verification error.                |
| `label`      | `string`                                | —            | Custom button label.                         |
| `variant`    | `'primary' \| 'secondary' \| 'outline'` | `'primary'`  | Button visual variant.                       |
| `size`       | `'sm' \| 'md' \| 'lg'`                  | `'md'`       | Button size.                                 |
| `autoVerify` | `boolean`                               | `false`      | Trigger verification automatically on mount. |

---

### `HumanityGate`

Conditionally renders children when a preset is verified; shows a fallback otherwise.

```tsx
<HumanityGate
  preset="kycPassed"
  fallback={<CompleteKYC />}
  loadingFallback={<Spinner />}
>
  <AgeRestrictedContent />
</HumanityGate>
```

Multiple presets (require all by default):

```tsx
<HumanityGate
  preset={['kyc_passed', 'accredited_investor']}
  match="all"
  fallback={<CompleteVerification />}
>
  <InvestorDashboard />
</HumanityGate>
```

**Props**

| Prop              | Type              | Default      | Description                                                          |
| ----------------- | ----------------- | ------------ | -------------------------------------------------------------------- |
| `preset`          | `string`          | **required** | Single preset key; gate passes when it's verified.                   |
| `presets`         | `string[]`        | —            | Multiple presets. Use with `requireAll` for AND/OR logic.            |
| `requireAll`      | `boolean`         | `true`       | When `true`, all presets must pass. When `false`, any one is enough. |
| `fallback`        | `React.ReactNode` | —            | Rendered when the gate condition is not met.                         |
| `loadingFallback` | `React.ReactNode` | —            | Rendered while verification is in progress.                          |

---

### `HumanityProfile`

Displays the authenticated user's profile with optional verification badges.

```tsx
<HumanityProfile
  variant="card"
  showAvatar={true}
  showBadges={true}
  showFields={['email', 'humanity_score']}
/>
```

**Props**

| Prop         | Type                              | Default  | Description                |
| ------------ | --------------------------------- | -------- | -------------------------- |
| `variant`    | `'card' \| 'inline' \| 'minimal'` | `'card'` | Display variant.           |
| `showAvatar` | `boolean`                         | `true`   | Show user avatar.          |
| `showBadges` | `boolean`                         | `true`   | Show verification badges.  |
| `showFields` | `string[]`                        | —        | Profile fields to display. |
| `className`  | `string`                          | —        | Additional CSS class.      |

---

### `HumanityErrorBoundary`

React error boundary for catching and displaying errors in the Humanity component tree.

```tsx
<HumanityErrorBoundary
  fallback={(error) => <p>Something went wrong: {error.message}</p>}
>
  <HumanityConnect />
</HumanityErrorBoundary>
```

**Props**

| Prop       | Type                                            | Description                           |
| ---------- | ----------------------------------------------- | ------------------------------------- |
| `fallback` | `(error: Error) => React.ReactNode`             | Render function for the error state.  |
| `onError`  | `(error: Error, info: React.ErrorInfo) => void` | Called when a child component throws. |
| `children` | `React.ReactNode`                               | The component tree to protect.        |

---

## Hooks

### `useHumanity()`

Full-access hook to the entire context. Throws if called outside a `<HumanityProvider>`.

```tsx
const {
  user,
  accessToken,
  authStatus,
  isAuthenticated,
  isLoading,
  error,
  login,
  logout,
  refreshToken,
  verify,
  verifyMultiple,
} = useHumanity()
```

---

### `useAuth()`

Auth-focused hook that selects only authentication state and operations. Thin wrapper around `useHumanity()`.

```tsx
import { useAuth } from '@humanity-org/react-sdk'

function LoginButton() {
  const { isAuthenticated, login, logout, isLoading } = useAuth()

  if (isLoading) return <Spinner />
  if (isAuthenticated) return <button onClick={logout}>Sign Out</button>
  return <button onClick={() => login()}>Sign In</button>
}
```

**Returns**

| Field             | Type                                                | Description                                  |
| ----------------- | --------------------------------------------------- | -------------------------------------------- |
| `authStatus`      | `'loading' \| 'authenticated' \| 'unauthenticated'` | Tri-state auth status.                       |
| `isAuthenticated` | `boolean`                                           | Shorthand: `authStatus === 'authenticated'`. |
| `isLoading`       | `boolean`                                           | Shorthand: `authStatus === 'loading'`.       |
| `user`            | `UserProfile \| null`                               | Current user profile, or null.               |
| `accessToken`     | `string \| null`                                    | Current access token, or null.               |
| `error`           | `HumanityReactError \| null`                        | Most recent auth error, or null.             |
| `login`           | `(options?: LoginOptions) => Promise<AuthResult>`   | Initiate OAuth login flow.                   |
| `logout`          | `() => Promise<void>`                               | Log out and revoke tokens.                   |
| `refreshToken`    | `() => Promise<AuthResult>`                         | Manually refresh the access token.           |
| `getAccessToken`  | `() => string \| null`                              | Get the current access token synchronously.  |

---

### `useVerification()`

Runs preset verifications against the Humanity Protocol API. Returns a **discriminated union** `status` for type-safe state narrowing.

```tsx
import { useVerification } from '@humanity-org/react-sdk'

function AgeCheck() {
  const { verify, status, result, isLoading } = useVerification()

  return (
    <div>
      <button onClick={() => verify('ageOver21')} disabled={isLoading}>
        Check Age
      </button>
      {status === 'success' && (
        <p>{result.verified ? '✅ Verified' : '❌ Not verified'}</p>
      )}
      {status === 'error' && <p>Verification failed</p>}
    </div>
  )
}
```

**Returns**

| Field            | Type                                                   | Description                                                      |
| ---------------- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| `status`         | `'idle' \| 'loading' \| 'success' \| 'error'`          | Discriminated union status. Narrow with `switch`/`if`.           |
| `result`         | `VerificationResult \| null`                           | Verification result when `status === 'success'`; null otherwise. |
| `error`          | `HumanityReactError \| null`                           | Error when `status === 'error'`; null otherwise.                 |
| `isLoading`      | `boolean`                                              | True while a request is in-flight.                               |
| `verify`         | `(preset: string) => Promise<VerificationResult>`      | Verify a single preset.                                          |
| `verifyMultiple` | `(presets: string[]) => Promise<VerificationResult[]>` | Verify multiple presets at once.                                 |
| `reset`          | `() => void`                                           | Reset to idle state.                                             |

---

### `usePresets()`

Manages multiple preset verifications. Stores results in an internal map for easy per-preset access.

```tsx
import { usePresets } from '@humanity-org/react-sdk'
import { useEffect } from 'react'

function Dashboard() {
  const { verifyAll, isVerified, getResult, isLoading } = usePresets()

  useEffect(() => {
    verifyAll(['ageOver18', 'kycPassed'])
  }, [verifyAll])

  if (isLoading) return <Spinner />

  return (
    <div>
      <p>Age 18+: {isVerified('ageOver18') ? '✅' : '❌'}</p>
      <p>KYC: {isVerified('kycPassed') ? '✅' : '❌'}</p>
    </div>
  )
}
```

**Returns**

| Field        | Type                                                   | Description                                      |
| ------------ | ------------------------------------------------------ | ------------------------------------------------ |
| `status`     | `'idle' \| 'loading' \| 'success' \| 'error'`          | Discriminated union status.                      |
| `results`    | `Map<string, VerificationResult>`                      | Map of preset key → verification result.         |
| `error`      | `HumanityReactError \| null`                           | Error when `status === 'error'`; null otherwise. |
| `isLoading`  | `boolean`                                              | True while a verification batch is in-flight.    |
| `verifyAll`  | `(presets: string[]) => Promise<VerificationResult[]>` | Verify a batch of presets; stores all results.   |
| `getResult`  | `(preset: string) => VerificationResult \| undefined`  | Get the cached result for a specific preset.     |
| `isVerified` | `(preset: string) => boolean`                          | Quick check: is the given preset verified?       |
| `reset`      | `() => void`                                           | Clear all stored results.                        |

---

### `useCredentialUpdates()`

Polls the Humanity Protocol API for credential updates. Useful for keeping verification status in sync.

```tsx
import { useCredentialUpdates } from '@humanity-org/react-sdk'

function CredentialDashboard() {
  const { data, isLoading, error, refetch } = useCredentialUpdates({
    pollInterval: 30_000,
    enabled: true,
  })

  if (isLoading && !data) return <Spinner />
  if (error) return <p>Failed to load credentials</p>

  return (
    <div>
      <p>Credentials: {data?.credentials.length ?? 0}</p>
      <button onClick={refetch}>Refresh</button>
    </div>
  )
}
```

**Options**

| Option         | Type      | Default | Description                                             |
| -------------- | --------- | ------- | ------------------------------------------------------- |
| `pollInterval` | `number`  | `0`     | Polling interval in milliseconds. `0` disables polling. |
| `enabled`      | `boolean` | `true`  | Whether polling is active.                              |

**Returns**

| Field       | Type                         | Description                         |
| ----------- | ---------------------------- | ----------------------------------- |
| `data`      | `CredentialUpdates \| null`  | Latest credential updates, or null. |
| `isLoading` | `boolean`                    | True while a request is in-flight.  |
| `error`     | `HumanityReactError \| null` | Most recent error, or null.         |
| `refetch`   | `() => Promise<void>`        | Manually trigger a fetch.           |

---

## TypeScript Types

All types are exported from `@humanity-org/react-sdk`.

### Key types

```typescript
// Auth state
type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated'
type Environment = 'production' | 'sandbox'
type StorageStrategy = 'memory' | 'localStorage' | 'sessionStorage'
type OAuthMode = 'popup' | 'redirect'

// User profile (returned after login)
interface UserProfile {
  sub: string // Unique user ID
  email?: string
  name?: string
  humanity_score?: number
  // ...additional fields from granted scopes
}

// Auth operation result
interface AuthResult {
  user: UserProfile
  accessToken: string
  // ...token metadata
}

// Verification result
interface VerificationResult {
  preset: string
  verified: boolean
  status: 'valid' | 'expired' | 'pending' | 'unavailable'
  expiresAt?: string
}

// Preset status
type PresetStatus = 'valid' | 'expired' | 'pending' | 'unavailable'
```

---

## Error Handling

The SDK exports typed error classes for precise error handling:

```tsx
import {
  isHumanityError,
  type HumanityReactError,
} from '@humanity-org/react-sdk'

function handleError(err: unknown) {
  if (isHumanityError(err)) {
    console.error('Humanity error:', {
      message: err.message,
      code: err.code,
      status: err.status,
    })
  }
}
```

**Using the global error handler:**

```tsx
<HumanityProvider
  clientId="hp_xxx"
  redirectUri="https://app.com/callback"
  onError={(error) => {
    Sentry.captureException(error)
  }}
>
  <App />
</HumanityProvider>
```

**Wrapping with an error boundary:**

```tsx
<HumanityErrorBoundary
  fallback={(error) => <ErrorPage message={error.message} />}
>
  <HumanityConnect onError={handleError} />
</HumanityErrorBoundary>
```

**Error utilities**

| Export            | Description                                                      |
| ----------------- | ---------------------------------------------------------------- |
| `isHumanityError` | Type guard: returns true if the value is a `HumanityReactError`. |
| `createError`     | Factory for typed SDK errors.                                    |
| `normalizeError`  | Converts any thrown value into a `HumanityReactError`.           |
| `parseOAuthError` | Parses OAuth error parameters from a callback URL.               |

---

## Testing

Use `MockHumanityProvider` for unit tests without real API calls:

```tsx
import { MockHumanityProvider } from '@humanity-org/react-sdk/testing'

test('shows dashboard when authenticated', () => {
  render(
    <MockHumanityProvider
      user={{ sub: 'test-123', email: 'test@example.com', name: 'Test User' }}
      isAuthenticated={true}
    >
      <Dashboard />
    </MockHumanityProvider>,
  )

  expect(screen.getByText('Welcome, Test User!')).toBeInTheDocument()
})

test('shows login when unauthenticated', () => {
  render(
    <MockHumanityProvider isAuthenticated={false}>
      <Dashboard />
    </MockHumanityProvider>,
  )

  expect(screen.getByText('Please log in')).toBeInTheDocument()
})
```

---

## Theming

Customize component appearance with CSS variables:

```css
:root {
  --humanity-primary: #000000;
  --humanity-primary-hover: #222222;
  --humanity-success: #10b981;
  --humanity-error: #ef4444;
  --humanity-border-radius: 8px;
  --humanity-font-family: inherit;
}
```

Set `theme="dark"` or `theme="light"` on `<HumanityProvider>` to control the built-in theme; `theme="system"` (default) follows the OS preference.

---

## Production Checklist

Before going live:

- [ ] OAuth popup flow works end-to-end across all supported browsers
- [ ] Redirect URI is registered for both sandbox and production environments
- [ ] Required scopes match what the user is prompted to grant
- [ ] `storage="memory"` is used (or you understand the XSS risk of `localStorage`)
- [ ] Error cases handled: expired tokens, revoked access, popup blocked
- [ ] `<HumanityErrorBoundary>` wraps authentication-critical UI
- [ ] `onError` global handler is configured for error monitoring
- [ ] `baseUrl` and `environment` point to production, not sandbox
- [ ] Client ID is correct for the production application

---

## License

MIT