npm package: @personql/react-native
Description: React Native SDK for PersonQL authentication and multi-tenant support
Author: PersonQL Team
Latest version: 1.0.6

# @personql/react-native

Official PersonQL SDK for React Native applications. Provides authentication, multi-tenant organization management, secure storage, and biometric authentication for iOS and Android.

## Features

- 🔐 **Secure Authentication** - Email/password, OAuth, and biometric authentication
- 🏢 **Multi-Tenant Organizations** - Complete organization management with RBAC
- 🔒 **Secure Storage** - Native Keychain (iOS) and Keystore (Android) integration
- 👆 **Biometric Auth** - Face ID, Touch ID, and Fingerprint support
- 🎨 **Pre-built UI** - Ready-to-use SignInScreen component
- 📱 **Cross-Platform** - Works on iOS and Android
- 🪝 **React Hooks** - Modern hooks-based API
- 💪 **TypeScript** - Full type safety

## Installation

```bash
npm install @personql/react-native
# or
yarn add @personql/react-native
# or
pnpm add @personql/react-native
```

### Peer Dependencies

Install required peer dependencies:

```bash
npm install react react-native @react-native-async-storage/async-storage react-native-keychain react-native-biometrics
```

### iOS Setup

```bash
cd ios && pod install
```

Add to your `Info.plist`:

```xml
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to sign in to PersonQL</string>
```

### Android Setup

No additional setup required for basic functionality.

## Quick Start

### 1. Setup Provider

Wrap your app with `PersonQLProvider`:

```tsx
import { PersonQLProvider } from '@personql/react-native';

function App() {
  return (
    <PersonQLProvider apiUrl="https://gateway.personql.com">
      <AppContent />
    </PersonQLProvider>
  );
}
```

### 2. Use the Pre-built SignInScreen

```tsx
import { SignInScreen } from '@personql/react-native';
import { useState } from 'react';

function AuthFlow() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  if (!isAuthenticated) {
    return (
      <SignInScreen
        apiUrl="https://gateway.personql.com"
        onSignInSuccess={() => setIsAuthenticated(true)}
        enableBiometric
      />
    );
  }

  return <Dashboard />;
}
```

### 3. Or Build Custom Auth UI

```tsx
import { useAuth } from '@personql/react-native';
import { View, TextInput, Button, Text } from 'react-native';
import { useState } from 'react';

function CustomSignIn() {
  const {
    signIn,
    signInWithBiometric,
    loading,
    error,
    biometricAvailable,
  } = useAuth({
    apiUrl: 'https://gateway.personql.com',
    enableBiometric: true,
  });

  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSignIn = async () => {
    try {
      await signIn({ email, password });
      // Navigate to app
    } catch (error) {
      console.error('Sign in failed:', error);
    }
  };

  return (
    <View>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      {error && <Text>{error.message}</Text>}
      <Button title="Sign In" onPress={handleSignIn} disabled={loading} />

      {biometricAvailable && (
        <Button
          title="Sign In with Biometric"
          onPress={signInWithBiometric}
          disabled={loading}
        />
      )}
    </View>
  );
}
```

## API Reference

### PersonQLProvider

Context provider that initializes PersonQL client.

```tsx
<PersonQLProvider
  apiUrl="https://gateway.personql.com"
  clientId="optional-client-id"
>
  {children}
</PersonQLProvider>
```

**Props:**
- `apiUrl` (required): Your PersonQL API endpoint
- `clientId` (optional): Client identifier for tracking
- `children`: React components

### useAuth Hook

Manages authentication state and operations.

```tsx
const {
  // State
  isAuthenticated,
  user,
  loading,
  error,
  biometricAvailable,

  // Auth methods
  signIn,
  signUp,
  signOut,
  signInWithBiometric,

  // Token methods
  refreshToken,
  isTokenExpired,

  // Biometric methods
  enableBiometricAuth,
  disableBiometricAuth,

  // Utility
  clearError,
} = useAuth({
  apiUrl: 'https://gateway.personql.com',
  enableBiometric: true,
  biometricPrompt: 'Authenticate to continue',
  onAuthStateChange: (isAuth) => console.log('Auth changed:', isAuth),
});
```

**Configuration:**
- `apiUrl` (required): Your PersonQL API endpoint
- `enableBiometric` (optional): Enable biometric authentication
- `biometricPrompt` (optional): Custom biometric prompt message
- `onAuthStateChange` (optional): Callback when auth state changes

**Methods:**

#### `signIn(credentials)`
Sign in with email and password.

```tsx
await signIn({
  email: 'user@example.com',
  password: 'password123',
});
```

#### `signUp(credentials)`
Create a new account.

```tsx
await signUp({
  email: 'user@example.com',
  password: 'password123',
  firstName: 'John',
  lastName: 'Doe',
});
```

#### `signOut()`
Sign out the current user.

```tsx
await signOut();
```

#### `signInWithBiometric()`
Authenticate using biometrics (Face ID, Touch ID, Fingerprint).

```tsx
await signInWithBiometric();
```

#### `refreshToken()`
Manually refresh the access token.

```tsx
await refreshToken();
```

#### `isTokenExpired()`
Check if the current token is expired.

```tsx
const expired = await isTokenExpired();
```

### useOrganization Hook

Manages multi-tenant organizations and permissions.

```tsx
const {
  // State
  currentOrganization,
  organizations,
  loading,
  switching,
  error,
  currentRole,
  members,
  roles,

  // Methods
  fetchOrganizations,
  switchOrganization,
  createOrganization,
  updateOrganization,
  hasPermission,
  loadMembers,
  loadRoles,
  refreshOrganizations,
  clearError,
} = useOrganization();
```

**Methods:**

#### `fetchOrganizations()`
Load all organizations for the current user.

```tsx
await fetchOrganizations();
```

#### `switchOrganization(organizationId)`
Switch to a different organization.

```tsx
await switchOrganization('org_123');
```

#### `createOrganization(data)`
Create a new organization.

```tsx
const org = await createOrganization({
  name: 'My Company',
  slug: 'my-company',
});
```

#### `updateOrganization(organizationId, updates)`
Update organization details.

```tsx
const updated = await updateOrganization('org_123', {
  name: 'New Name',
  primary_color: '#007AFF',
});
```

#### `hasPermission(permission)`
Check if current user has a specific permission.

```tsx
if (hasPermission('users:write')) {
  // User can create/edit users
}

if (hasPermission('billing:*')) {
  // User has all billing permissions
}
```

**Permission Format:**
- `resource:action` - e.g., `users:read`, `billing:write`
- `resource:*` - All actions for a resource
- `*` - All permissions (superuser)

#### `loadMembers(organizationId?)`
Load members of an organization.

```tsx
await loadMembers(); // Current org
await loadMembers('org_123'); // Specific org
```

#### `loadRoles(organizationId?)`
Load roles for an organization.

```tsx
await loadRoles(); // Current org
await loadRoles('org_123'); // Specific org
```

### SignInScreen Component

Pre-built sign-in screen with email/password and biometric authentication.

```tsx
<SignInScreen
  apiUrl="https://gateway.personql.com"
  onSignInSuccess={() => navigation.navigate('Dashboard')}
  onSignUpPress={() => navigation.navigate('SignUp')}
  onForgotPasswordPress={() => navigation.navigate('ForgotPassword')}
  Logo={MyLogoComponent}
  primaryColor="#007AFF"
  enableBiometric
  biometricPrompt="Sign in to continue"
/>
```

**Props:**
- `apiUrl` (required): Your PersonQL API endpoint
- `onSignInSuccess`: Callback when sign in succeeds
- `onSignUpPress`: Callback when user taps "Sign Up"
- `onForgotPasswordPress`: Callback when user taps "Forgot Password"
- `Logo`: Custom logo component
- `primaryColor`: Theme color for buttons and links
- `enableBiometric`: Enable biometric authentication
- `biometricPrompt`: Custom biometric prompt message

### SecureStorage

Low-level secure storage using native Keychain/Keystore.

```tsx
import { SecureStorage } from '@personql/react-native';

const storage = new SecureStorage({
  service: 'com.myapp', // Optional
});

// Store
await storage.setItem('key', 'value');

// Retrieve
const value = await storage.getItem('key');

// Remove
await storage.removeItem('key');

// Clear all
await storage.clear();

// Check availability
const available = await storage.isAvailable();
```

### TokenManager

Manages authentication tokens securely.

```tsx
import { TokenManager } from '@personql/react-native';

const tokenManager = new TokenManager();

// Save tokens
await tokenManager.saveTokens(
  'access_token',
  'refresh_token',
  Date.now() + 3600000 // Expiry timestamp
);

// Get tokens
const accessToken = await tokenManager.getAccessToken();
const refreshToken = await tokenManager.getRefreshToken();

// Check expiry
const expired = await tokenManager.isTokenExpired();

// Clear tokens
await tokenManager.clearTokens();

// Organization management
await tokenManager.saveCurrentOrganization('org_123');
const orgId = await tokenManager.getCurrentOrganization();
await tokenManager.clearCurrentOrganization();
```

### BiometricAuth

Biometric authentication wrapper.

```tsx
import { BiometricAuth } from '@personql/react-native';

const biometric = new BiometricAuth();

// Check availability
const capabilities = await biometric.checkAvailability();
if (capabilities.available) {
  console.log('Biometry type:', capabilities.biometryType);
  // 'FaceID', 'TouchID', or 'Biometrics'
}

// Authenticate
const success = await biometric.authenticate('Sign in to continue');

// Get biometry type name
const typeName = await biometric.getBiometryTypeName();
// "Face ID", "Touch ID", or "Biometric Authentication"

// Advanced: Create keys for signing
const keys = await biometric.createKeys();
if (keys) {
  console.log('Public key:', keys.publicKey);
}

// Advanced: Create signature
const signature = await biometric.createSignature('data_to_sign');

// Delete keys
await biometric.deleteKeys();
```

## Multi-Tenant Examples

### Organization Switcher

```tsx
import { useOrganization } from '@personql/react-native';
import { View, Text, TouchableOpacity, FlatList } from 'react-native';

function OrganizationSwitcher() {
  const {
    currentOrganization,
    organizations,
    switchOrganization,
    switching,
  } = useOrganization();

  return (
    <FlatList
      data={organizations}
      keyExtractor={(org) => org.id}
      renderItem={({ item }) => (
        <TouchableOpacity
          onPress={() => switchOrganization(item.id)}
          disabled={switching || item.id === currentOrganization?.id}
        >
          <Text>{item.name}</Text>
          {item.id === currentOrganization?.id && <Text>✓ Active</Text>}
        </TouchableOpacity>
      )}
    />
  );
}
```

### Permission-Based Rendering

```tsx
import { useOrganization } from '@personql/react-native';
import { View, Button } from 'react-native';

function UserManagement() {
  const { hasPermission } = useOrganization();

  return (
    <View>
      {hasPermission('users:read') && (
        <Button title="View Users" onPress={viewUsers} />
      )}

      {hasPermission('users:write') && (
        <Button title="Add User" onPress={addUser} />
      )}

      {hasPermission('users:delete') && (
        <Button title="Delete User" onPress={deleteUser} />
      )}
    </View>
  );
}
```

### Create Organization

```tsx
import { useOrganization } from '@personql/react-native';
import { View, TextInput, Button } from 'react-native';
import { useState } from 'react';

function CreateOrganization() {
  const { createOrganization } = useOrganization();
  const [name, setName] = useState('');
  const [slug, setSlug] = useState('');

  const handleCreate = async () => {
    try {
      const org = await createOrganization({ name, slug });
      console.log('Created:', org);
      // Navigation or success handling
    } catch (error) {
      console.error('Failed to create organization:', error);
    }
  };

  return (
    <View>
      <TextInput
        placeholder="Organization Name"
        value={name}
        onChangeText={setName}
      />
      <TextInput
        placeholder="Slug (URL-friendly)"
        value={slug}
        onChangeText={setSlug}
        autoCapitalize="none"
      />
      <Button title="Create" onPress={handleCreate} />
    </View>
  );
}
```

## Types

### User

```typescript
interface User {
  id: string;
  email: string;
  firstName?: string;
  lastName?: string;
  avatar?: string;
  createdAt: string;
  updatedAt: string;
}
```

### Organization

```typescript
interface Organization {
  id: string;
  name: string;
  slug: string;
  domain?: string;
  logo_url?: string;
  primary_color?: string;
  plan_type: 'free' | 'starter' | 'pro' | 'enterprise';
  max_users: number;
  max_api_calls: number;
  settings?: Record<string, any>;
  status: 'active' | 'suspended' | 'deleted';
  created_at: string;
  updated_at: string;
}
```

### Role

```typescript
interface Role {
  id: string;
  organization_id: string;
  name: string;
  description?: string;
  permissions: string[];
  is_system: boolean;
  created_at: string;
  updated_at: string;
}
```

### OrganizationMember

```typescript
interface OrganizationMember {
  id: string;
  organization_id: string;
  user_id: string;
  role_id: string;
  status: 'pending' | 'active' | 'suspended';
  created_at: string;
  updated_at: string;
}
```

## Security Best Practices

1. **Never log sensitive data** - Avoid logging tokens, passwords, or biometric data
2. **Use HTTPS** - Always use HTTPS endpoints in production
3. **Token expiry** - Tokens expire automatically; use `refreshToken()` as needed
4. **Biometric fallback** - Always provide password fallback for biometric auth
5. **Keychain access** - Data is encrypted at rest using native APIs
6. **Permission checking** - Always check permissions before showing/allowing actions

## Troubleshooting

### iOS Biometric Not Working

Make sure you've added `NSFaceIDUsageDescription` to `Info.plist`:

```xml
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to sign in</string>
```

### Android Keystore Issues

If you encounter keystore errors, try clearing app data or reinstalling.

### Token Refresh Fails

Ensure your refresh token hasn't expired. Tokens have a limited lifetime.

### Organization Not Switching

Make sure the user has access to the organization and it's in `active` status.

## Support

- **Documentation**: [https://docs.personql.com](https://docs.personql.com)
- **GitHub**: [https://github.com/FinHub-vc/PersonQL](https://github.com/FinHub-vc/PersonQL)
- **Issues**: [https://github.com/FinHub-vc/PersonQL/issues](https://github.com/FinHub-vc/PersonQL/issues)

## License

MIT License - see LICENSE file for details