npm package: @keyra/react
Description: React components and hooks for Keyra authentication - Clerk alternative
Author: Keyra
Latest version: 1.1.1

# @keyra/react

React components and hooks for Keyra authentication - a modern authentication platform for developers.

## Installation

```bash
npm install @keyra/react @keyra/core
```

## Quick Start

### 1. Set Environment Variables

Create `.env`:

```bash
# For Vite
VITE_KEYRA_PUBLISHABLE_KEY=pk_test_YOUR_KEY

# For Create React App
REACT_APP_KEYRA_PUBLISHABLE_KEY=pk_test_YOUR_KEY
```

### 2. Wrap Your App with KeyraProvider

**For Vite** (`src/main.tsx`):

```tsx
import { KeyraProvider } from '@keyra/react'
import ReactDOM from 'react-dom/client'
import App from './App'

const publishableKey = import.meta.env.VITE_KEYRA_PUBLISHABLE_KEY

ReactDOM.createRoot(document.getElementById('root')!).render(
  <KeyraProvider publishableKey={publishableKey}>
    <App />
  </KeyraProvider>
)
```

**For Create React App** (`src/index.tsx`):

```tsx
import { KeyraProvider } from '@keyra/react'
import ReactDOM from 'react-dom/client'
import App from './App'

const publishableKey = process.env.REACT_APP_KEYRA_PUBLISHABLE_KEY!

const root = ReactDOM.createRoot(document.getElementById('root')!)
root.render(
  <KeyraProvider publishableKey={publishableKey}>
    <App />
  </KeyraProvider>
)
```

### 3. Add Authentication to Your App

```tsx
import {
  SignIn,
  SignUp,
  SignedIn,
  SignedOut,
  UserButton,
  SignInButton,
  useUser,
} from '@keyra/react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Header />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/sign-in" element={<SignIn />} />
        <Route path="/sign-up" element={<SignUp />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </BrowserRouter>
  )
}

function Header() {
  return (
    <header>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </header>
  )
}

function HomePage() {
  return (
    <div>
      <SignedOut>
        <h1>Welcome! Please sign in.</h1>
      </SignedOut>
      <SignedIn>
        <h1>You are signed in!</h1>
      </SignedIn>
    </div>
  )
}

function Dashboard() {
  const { user } = useUser()
  return <h1>Welcome {user?.firstName}!</h1>
}
```

---

## Components

### `<KeyraProvider>`

Root provider component that enables Keyra authentication.

```tsx
<KeyraProvider
  publishableKey="pk_test_..."
  navigate={(to) => router.push(to)}
  appearance={{
    baseTheme: 'dark',
    variables: {
      colorPrimary: '#6366f1',
    },
  }}
>
  {children}
</KeyraProvider>
```

**Props:**
- `publishableKey` (required): Your Keyra publishable key
- `navigate` (optional): Custom navigation function
- `appearance` (optional): Theme customization

### `<SignIn />`

Pre-built sign-in component with customizable appearance.

```tsx
<SignIn 
  redirectUrl="/dashboard"
  signUpUrl="/sign-up"
  appearance={{
    baseTheme: 'dark',
    variables: {
      colorPrimary: '#6366f1',
    },
  }}
/>
```

**Props:**
- `redirectUrl`: Where to redirect after sign-in
- `signUpUrl`: URL for sign-up link
- `appearance`: Theme customization

### `<SignUp />`

Pre-built sign-up component.

```tsx
<SignUp 
  redirectUrl="/onboarding"
  signInUrl="/sign-in"
  appearance={{
    baseTheme: 'dark',
  }}
/>
```

**Props:**
- `redirectUrl`: Where to redirect after sign-up
- `signInUrl`: URL for sign-in link
- `appearance`: Theme customization

### `<UserButton />`

User menu button with profile and sign-out options.

```tsx
<UserButton 
  afterSignOutUrl="/"
  showName={true}
/>
```

**Props:**
- `afterSignOutUrl`: Where to redirect after sign-out
- `showName`: Display user name next to avatar

### `<SignInButton />`

Button to trigger sign-in flow.

```tsx
<SignInButton mode="modal" redirectUrl="/dashboard">
  <button>Sign In</button>
</SignInButton>
```

**Props:**
- `mode`: `'redirect'` | `'modal'` (default: `'redirect'`)
- `redirectUrl`: Where to redirect after sign-in
- `children`: Custom button element (optional)

### `<SignOutButton />`

Button to sign out the current user.

```tsx
<SignOutButton redirectUrl="/">
  <button>Sign Out</button>
</SignOutButton>
```

**Props:**
- `redirectUrl`: Where to redirect after sign-out
- `children`: Custom button element (optional)

### `<SignedIn>` / `<SignedOut>`

Conditional rendering based on authentication state.

```tsx
<SignedIn>
  <Dashboard />
</SignedIn>

<SignedOut>
  <LandingPage />
</SignedOut>
```

### `<Protect>`

Protect content with role or permission checks.

```tsx
<Protect 
  role="admin" 
  fallback={<div>You need admin access</div>}
>
  <AdminPanel />
</Protect>
```

**Props:**
- `role`: Required role
- `permission`: Required permission
- `fallback`: Component to show if check fails

### `<OrganizationSwitcher>`

Organization selection and management.

```tsx
<OrganizationSwitcher 
  afterSelectOrganizationUrl="/dashboard"
/>
```

**Props:**
- `afterSelectOrganizationUrl`: Where to redirect after selecting org

### `<RedirectToSignIn>` / `<RedirectToSignUp>`

Redirect helpers.

```tsx
<RedirectToSignIn returnBackUrl="/protected" />
<RedirectToSignUp />
```

**Props:**
- `returnBackUrl`: Where to return after authentication

---

## Hooks

### `useAuth()`

Access authentication state and methods.

```tsx
const { 
  isLoaded,      // Auth state is loaded
  isSignedIn,    // User is signed in
  userId,        // Current user ID
  sessionId,     // Current session ID
  orgId,         // Current organization ID
  signOut,       // Sign out function
  getToken,      // Get session token
} = useAuth()
```

**Example:**

```tsx
function Header() {
  const { isSignedIn, signOut } = useAuth()
  
  return (
    <header>
      {isSignedIn && (
        <button onClick={() => signOut()}>Sign Out</button>
      )}
    </header>
  )
}
```

### `useUser()`

Access current user information.

```tsx
const { 
  user,         // User object
  isLoaded,     // User data is loaded
  isSignedIn,   // User is signed in
} = useUser()
```

**Example:**

```tsx
function Profile() {
  const { user, isLoaded, isSignedIn } = useUser()

  if (!isLoaded) return <div>Loading...</div>
  if (!isSignedIn) return <div>Not signed in</div>

  return (
    <div>
      <h1>Hello {user.firstName}!</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}
```

### `useSession()`

Access current session information.

```tsx
const { 
  session,     // Session object
  isLoaded,    // Session data is loaded
  isSignedIn,  // User is signed in
} = useSession()
```

**Example:**

```tsx
function SessionInfo() {
  const { session } = useSession()
  
  return (
    <div>
      Session ID: {session?.id}
      Expires: {session?.expireAt?.toLocaleDateString()}
    </div>
  )
}
```

### `useOrganization()`

Access current organization information.

```tsx
const { 
  organization,  // Organization object
  isLoaded,      // Org data is loaded
} = useOrganization()
```

**Example:**

```tsx
function OrgHeader() {
  const { organization, isLoaded } = useOrganization()
  
  if (!isLoaded) return <div>Loading...</div>
  
  return (
    <div>
      Current Org: {organization?.name || 'Personal'}
    </div>
  )
}
```

### `useSignIn()`

Access sign-in methods and state.

```tsx
const { 
  signIn,        // Sign-in resource
  isLoaded,      // Sign-in is loaded
  setActive,     // Set active session
} = useSignIn()
```

**Example - Custom Sign-In Form:**

```tsx
function CustomSignIn() {
  const { signIn, setActive } = useSignIn()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const navigate = useNavigate()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    try {
      const result = await signIn.create({
        identifier: email,
        password,
      })
      
      if (result.status === 'complete') {
        await setActive({ session: result.createdSessionId })
        navigate('/dashboard')
      }
    } catch (error) {
      console.error('Sign-in error:', error)
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit">Sign In</button>
    </form>
  )
}
```

### `useSignUp()`

Access sign-up methods and state.

```tsx
const { 
  signUp,       // Sign-up resource
  isLoaded,     // Sign-up is loaded
  setActive,    // Set active session
} = useSignUp()
```

**Example - Custom Sign-Up Form:**

```tsx
function CustomSignUp() {
  const { signUp, setActive } = useSignUp()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [firstName, setFirstName] = useState('')
  const navigate = useNavigate()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    try {
      const result = await signUp.create({
        emailAddress: email,
        password,
        firstName,
      })
      
      if (result.status === 'complete') {
        await setActive({ session: result.createdSessionId })
        navigate('/dashboard')
      }
    } catch (error) {
      console.error('Sign-up error:', error)
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
        placeholder="First Name"
        required
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit">Sign Up</button>
    </form>
  )
}
```

### `useSessionList()`

Manage multiple sessions.

```tsx
const { 
  sessions,    // Array of sessions
  setActive,   // Set active session
} = useSessionList()
```

**Example - Session Switcher:**

```tsx
function SessionSwitcher() {
  const { sessions, setActive } = useSessionList()
  
  return (
    <div>
      <h3>Active Sessions</h3>
      {sessions.map(session => (
        <div key={session.id}>
          <span>{session.user.email}</span>
          <button onClick={() => setActive({ session: session.id })}>
            Switch
          </button>
        </div>
      ))}
    </div>
  )
}
```

### `useKeyra()`

Access the complete Keyra context.

```tsx
const { 
  user,          // Current user
  session,       // Current session
  organization,  // Current organization
  signIn,        // Sign-in resource
  signUp,        // Sign-up resource
  signOut,       // Sign out function
  setActive,     // Set active session/org
} = useKeyra()
```

---

## Appearance Customization

Customize the appearance of Keyra components:

### Basic Theme

```tsx
const appearance = {
  baseTheme: 'dark', // 'light' | 'dark'
  variables: {
    colorPrimary: '#6366f1',
    colorBackground: '#1f2937',
    colorText: '#f9fafb',
    borderRadius: '8px',
  },
}

<SignIn appearance={appearance} />
```

### Advanced Customization

```tsx
const appearance = {
  baseTheme: 'dark',
  variables: {
    colorPrimary: '#6366f1',
    colorBackground: '#1f2937',
    colorText: '#f9fafb',
    colorDanger: '#ef4444',
    colorSuccess: '#10b981',
    fontFamily: '"Inter", sans-serif',
    borderRadius: '8px',
  },
  elements: {
    formButtonPrimary: {
      backgroundColor: '#6366f1',
      color: '#ffffff',
      '&:hover': {
        backgroundColor: '#4f46e5',
      },
    },
    formFieldInput: {
      borderRadius: '6px',
      borderColor: '#374151',
    },
    card: {
      boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
    },
  },
}
```

### Apply to KeyraProvider

```tsx
<KeyraProvider
  publishableKey={publishableKey}
  appearance={appearance}
>
  {children}
</KeyraProvider>
```

---

## TypeScript Support

Full TypeScript support with exported types:

```typescript
import type { 
  User,
  Session,
  Organization,
  SignInResource,
  SignUpResource,
  SignInParams,
  SignUpParams,
  KeyraProviderProps,
  SignInProps,
  SignUpProps,
  UserButtonProps,
} from '@keyra/react'
```

---

## Protected Routes

### With React Router

```tsx
import { useAuth } from '@keyra/react'
import { Navigate } from 'react-router-dom'

function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isSignedIn, isLoaded } = useAuth()
  
  if (!isLoaded) {
    return <div>Loading...</div>
  }
  
  if (!isSignedIn) {
    return <Navigate to="/sign-in" replace />
  }
  
  return <>{children}</>
}

// Usage
<Route 
  path="/dashboard" 
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } 
/>
```

### With Role Check

```tsx
function AdminRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoaded } = useUser()
  
  if (!isLoaded) return <div>Loading...</div>
  
  if (user?.publicMetadata?.role !== 'admin') {
    return <Navigate to="/" replace />
  }
  
  return <>{children}</>
}
```

---

## Error Handling

```tsx
import { isKeyraError, getErrorMessage } from '@keyra/core'

function CustomSignIn() {
  const { signIn } = useSignIn()
  const [error, setError] = useState('')

  const handleSubmit = async (data) => {
    try {
      await signIn.create(data)
    } catch (err) {
      if (isKeyraError(err)) {
        setError(getErrorMessage(err))
      } else {
        setError('An unexpected error occurred')
      }
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}
      {/* form fields */}
    </form>
  )
}
```

---

## Examples

### Complete React App

```tsx
// main.tsx
import { KeyraProvider } from '@keyra/react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <KeyraProvider publishableKey={import.meta.env.VITE_KEYRA_PUBLISHABLE_KEY}>
    <App />
  </KeyraProvider>
)

// App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import {
  SignIn,
  SignUp,
  SignedIn,
  SignedOut,
  UserButton,
  SignInButton,
  useAuth,
} from '@keyra/react'

function App() {
  return (
    <BrowserRouter>
      <Header />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/sign-in" element={<SignIn />} />
        <Route path="/sign-up" element={<SignUp />} />
        <Route 
          path="/dashboard" 
          element={
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          } 
        />
      </Routes>
    </BrowserRouter>
  )
}

function Header() {
  return (
    <header>
      <h1>My App</h1>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </header>
  )
}

function HomePage() {
  return (
    <div>
      <SignedOut>
        <h1>Welcome! Please sign in.</h1>
      </SignedOut>
      <SignedIn>
        <h1>You are signed in!</h1>
      </SignedIn>
    </div>
  )
}

function Dashboard() {
  const { user } = useUser()
  return <h1>Welcome {user?.firstName}!</h1>
}

function ProtectedRoute({ children }) {
  const { isSignedIn, isLoaded } = useAuth()
  
  if (!isLoaded) return <div>Loading...</div>
  if (!isSignedIn) return <Navigate to="/sign-in" replace />
  
  return <>{children}</>
}
```

---

## Troubleshooting

### "publishableKey is required"

Make sure you've set the environment variable and passed it to `KeyraProvider`:

```tsx
<KeyraProvider publishableKey={import.meta.env.VITE_KEYRA_PUBLISHABLE_KEY}>
```

### Components not rendering

Ensure your app is wrapped with `KeyraProvider`:

```tsx
<KeyraProvider publishableKey={publishableKey}>
  <App />
</KeyraProvider>
```

### Hooks returning undefined

Make sure you're using hooks inside a component that's a child of `KeyraProvider`.

---

## Migration from Clerk

Keyra is designed to be API-compatible with Clerk. To migrate:

1. Replace `@clerk/clerk-react` with `@keyra/react`
2. Update environment variables from `CLERK_*` to `KEYRA_*`
3. Replace `ClerkProvider` with `KeyraProvider`
4. Update imports from `@clerk/clerk-react` to `@keyra/react`

Most code should work without changes!

---

## License

Proprietary - Commercial software. See your Keyra subscription for usage terms.

---

## Links

- [Documentation](https://keyra.app/docs)
- [GitHub](https://github.com/yourusername/keyra)
- [Examples](https://github.com/yourusername/keyra/tree/main/packages/examples)