@personql/react
v1.0.7
Published
React SDK for PersonQL authentication and user management
Maintainers
Readme
PersonQL React SDK
A comprehensive React SDK for PersonQL authentication and user management. This SDK provides pre-built components, hooks, and utilities to quickly integrate PersonQL authentication into your React applications.
Features
- 🚀 Out-of-the-box components - Ready-to-use Sign In, Sign Up, MFA, and Profile forms
- 🎣 React Hooks - Easy-to-use hooks for authentication state management
- 🔒 Security First - Built-in form validation, error handling, and secure token management
- 🎨 Customizable UI - Themeable components with Tailwind CSS
- 📱 Responsive Design - Mobile-friendly components
- 🔄 MFA Support - Multi-factor authentication with SMS, Email, and WhatsApp
- 🛡️ Route Protection - AuthGuard component for protecting routes
- 📝 TypeScript Support - Full TypeScript definitions included
Installation
npm install @personql/react
# or
yarn add @personql/reactQuick Start
1. Setup the Provider
Wrap your app with the PersonQLProvider:
import React from 'react';
import { PersonQLProvider } from '@personql/react';
const config = {
apiUrl: 'https://gateway.personql.com',
clientId: 'your-client-id',
};
function App() {
return (
<PersonQLProvider config={config}>
<YourAppContent />
</PersonQLProvider>
);
}2. Use Authentication Components
import React, { useState } from 'react';
import { SignInForm, SignUpForm, useAuth } from '@personql/react';
function AuthPage() {
const [showSignUp, setShowSignUp] = useState(false);
const { isAuthenticated } = useAuth();
if (isAuthenticated) {
return <Dashboard />;
}
return (
<div className="min-h-screen flex items-center justify-center">
{showSignUp ? (
<SignUpForm
onSuccess={() => console.log('Sign up successful!')}
onSignIn={() => setShowSignUp(false)}
/>
) : (
<SignInForm
onSuccess={() => console.log('Sign in successful!')}
onSignUp={() => setShowSignUp(true)}
/>
)}
</div>
);
}3. Protect Routes with AuthGuard
import React from 'react';
import { AuthGuard } from '@personql/react';
function ProtectedPage() {
return (
<AuthGuard
fallback={<div>Please sign in to access this page</div>}
requireVerification={true}
>
<h1>Protected Content</h1>
<p>Only authenticated and verified users can see this.</p>
</AuthGuard>
);
}Components
Authentication Forms
SignInForm
import { SignInForm } from '@personql/react';
<SignInForm
onSuccess={() => navigate('/dashboard')}
onSignUp={() => setShowSignUp(true)}
onForgotPassword={() => setShowForgotPassword(true)}
className="max-w-md mx-auto"
/>;SignUpForm
import { SignUpForm } from '@personql/react';
<SignUpForm
onSuccess={() => navigate('/dashboard')}
onSignIn={() => setShowSignUp(false)}
className="max-w-md mx-auto"
/>;MFAForm
import { MFAForm } from '@personql/react';
<MFAForm
onSuccess={() => navigate('/dashboard')}
onCancel={() => navigate('/signin')}
/>;ForgotPasswordForm
import { ForgotPasswordForm } from '@personql/react';
<ForgotPasswordForm
onSuccess={() => setEmailSent(true)}
onBackToSignIn={() => navigate('/signin')}
/>;ResetPasswordForm
import { ResetPasswordForm } from '@personql/react';
<ResetPasswordForm token={resetToken} onSuccess={() => navigate('/signin')} />;ProfileForm
import { ProfileForm } from '@personql/react';
<ProfileForm
onSuccess={() => setShowSuccess(true)}
className="max-w-2xl mx-auto"
/>;Route Protection
AuthGuard
import { AuthGuard } from '@personql/react';
<AuthGuard
fallback={<SignInPrompt />}
redirectTo="/signin"
requireVerification={true}
>
<ProtectedContent />
</AuthGuard>;Hooks
useAuth
Main authentication hook:
import { useAuth } from '@personql/react';
function MyComponent() {
const {
// State
isAuthenticated,
isLoading,
error,
user,
requiresMFA,
mfaMethods,
// Methods
signIn,
signUp,
signOut,
sendMFACode,
verifyMFACode,
forgotPassword,
resetPassword,
clearError,
isSessionExpired,
} = useAuth();
const handleSignIn = async () => {
try {
await signIn({
email: '[email protected]',
password: 'password123',
rememberMe: true,
});
} catch (error) {
console.error('Sign in failed:', error);
}
};
return (
<div>
{isAuthenticated ? (
<p>Welcome, {user?.firstName}!</p>
) : (
<button onClick={handleSignIn}>Sign In</button>
)}
</div>
);
}useUser
User-specific hook:
import { useUser } from '@personql/react';
function UserProfile() {
const {
// State
user,
isLoading,
error,
// Methods
updateProfile,
refreshUser,
// Computed values
isVerified,
fullName,
initials,
} = useUser();
const handleUpdateProfile = async () => {
await updateProfile({
firstName: 'John',
lastName: 'Doe',
});
};
return (
<div>
<h1>Hello, {fullName}!</h1>
<p>Verification status: {isVerified ? 'Verified' : 'Pending'}</p>
<div className="avatar">{initials}</div>
</div>
);
}usePersonQL
Access to the full PersonQL context:
import { usePersonQL } from '@personql/react';
function MyComponent() {
const {
authState,
config,
signIn,
signUp,
// ... all auth methods
} = usePersonQL();
return <div>...</div>;
}Configuration
PersonQLConfig
interface PersonQLConfig {
apiUrl: string; // Your Gateway Worker URL
clientId: string; // Your application client ID
redirectUri?: string; // Redirect URI after auth
scopes?: string[]; // Permission scopes
theme?: Theme; // UI customization
}
interface Theme {
primaryColor?: string;
backgroundColor?: string;
textColor?: string;
borderColor?: string;
borderRadius?: string;
fontFamily?: string;
}Example with Custom Theme
const config = {
apiUrl: 'https://gateway.personql.com',
clientId: 'your-client-id',
theme: {
primaryColor: '#3b82f6',
backgroundColor: '#ffffff',
textColor: '#1f2937',
borderColor: '#d1d5db',
borderRadius: '0.375rem',
fontFamily: 'Inter, sans-serif',
},
};
<PersonQLProvider config={config}>
<App />
</PersonQLProvider>;Error Handling
The SDK provides comprehensive error handling:
import { useAuth } from '@personql/react';
function MyComponent() {
const { error, clearError } = useAuth();
if (error) {
return (
<Alert
variant="error"
title={error.code}
message={error.message}
dismissible
onDismiss={clearError}
/>
);
}
return <div>No errors</div>;
}Validation
Built-in form validation utilities:
import { validateEmail, validatePassword } from '@personql/react';
const emailValidation = validateEmail('[email protected]');
const passwordValidation = validatePassword('password123', {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
});
if (!emailValidation.isValid) {
console.error(emailValidation.message);
}TypeScript Support
The SDK is fully typed with TypeScript:
import type {
PersonQLConfig,
AuthState,
User,
SignInCredentials,
SignUpCredentials,
MFACredentials,
AuthError,
} from '@personql/react';Examples
Complete Authentication Flow
import React, { useState } from 'react';
import {
PersonQLProvider,
SignInForm,
SignUpForm,
MFAForm,
ForgotPasswordForm,
ResetPasswordForm,
useAuth,
} from '@personql/react';
const config = {
apiUrl: 'https://gateway.personql.com',
clientId: 'your-client-id',
};
function AuthFlow() {
const [view, setView] = useState<
'signin' | 'signup' | 'mfa' | 'forgot' | 'reset'
>('signin');
const { isAuthenticated, requiresMFA } = useAuth();
if (isAuthenticated) {
return <Dashboard />;
}
if (requiresMFA) {
return (
<MFAForm
onSuccess={() => setView('signin')}
onCancel={() => setView('signin')}
/>
);
}
return (
<div className="min-h-screen flex items-center justify-center">
{view === 'signin' && (
<SignInForm
onSuccess={() => console.log('Signed in!')}
onSignUp={() => setView('signup')}
onForgotPassword={() => setView('forgot')}
/>
)}
{view === 'signup' && (
<SignUpForm
onSuccess={() => console.log('Signed up!')}
onSignIn={() => setView('signin')}
/>
)}
{view === 'forgot' && (
<ForgotPasswordForm
onSuccess={() => setView('signin')}
onBackToSignIn={() => setView('signin')}
/>
)}
{view === 'reset' && (
<ResetPasswordForm
token="reset-token"
onSuccess={() => setView('signin')}
/>
)}
</div>
);
}
function App() {
return (
<PersonQLProvider config={config}>
<AuthFlow />
</PersonQLProvider>
);
}Multi-Tenant Support
PersonQL React SDK includes built-in support for multi-tenant applications with organizations, roles, and permissions.
useOrganization Hook
Manage organizations in your multi-tenant application:
import { useOrganization } from '@personql/react';
function MyComponent() {
const {
// State
currentOrganization,
organizations,
loading,
switching,
currentRole,
members,
roles,
// Methods
switchOrganization,
createOrganization,
updateOrganization,
hasPermission,
loadMembers,
loadRoles,
refreshOrganizations,
} = useOrganization();
return (
<div>
<h2>{currentOrganization?.name}</h2>
{hasPermission('users:write') && (
<button>Add Team Member</button>
)}
</div>
);
}OrganizationSwitcher Component
Pre-built UI component for switching organizations:
import { OrganizationSwitcher } from '@personql/react';
function Header() {
return (
<header>
<OrganizationSwitcher
onOrganizationChange={(orgId) => {
console.log('Switched to:', orgId);
}}
showCreateButton={true}
onCreateClick={() => setShowCreateModal(true)}
/>
</header>
);
}Creating Organizations
import { useOrganization } from '@personql/react';
function CreateOrganizationButton() {
const { createOrganization } = useOrganization();
const handleCreate = async () => {
try {
const newOrg = await createOrganization({
name: 'My New Organization',
slug: 'my-new-org',
});
console.log('Created:', newOrg);
} catch (error) {
console.error('Failed to create organization:', error);
}
};
return <button onClick={handleCreate}>Create Organization</button>;
}Switching Organizations
import { useOrganization } from '@personql/react';
function OrganizationList() {
const { organizations, currentOrganization, switchOrganization } = useOrganization();
return (
<ul>
{organizations.map((org) => (
<li key={org.id}>
<button
onClick={() => switchOrganization(org.id)}
disabled={org.id === currentOrganization?.id}
>
{org.name}
{org.id === currentOrganization?.id && ' (Current)'}
</button>
</li>
))}
</ul>
);
}Permission-Based Rendering
import { useOrganization } from '@personql/react';
function AdminPanel() {
const { hasPermission } = useOrganization();
if (!hasPermission('settings:write')) {
return <div>Access Denied</div>;
}
return (
<div>
<h1>Admin Settings</h1>
{/* Admin content */}
</div>
);
}Loading Organization Members
import { useOrganization } from '@personql/react';
import { useEffect } from 'react';
function TeamMembers() {
const { members, loadMembers, currentOrganization } = useOrganization();
useEffect(() => {
if (currentOrganization) {
loadMembers();
}
}, [currentOrganization?.id]);
return (
<div>
<h2>Team Members</h2>
<ul>
{members.map((member) => (
<li key={member.id}>User ID: {member.user_id}</li>
))}
</ul>
</div>
);
}Organization Types
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;
}
interface Role {
id: string;
organization_id: string;
name: string;
description?: string;
permissions: string[];
is_system: boolean;
created_at: string;
updated_at: string;
}
interface OrganizationMember {
id: string;
organization_id: string;
user_id: string;
role_id: string;
status: 'pending' | 'active' | 'suspended';
created_at: string;
updated_at: string;
}Permission Format
Permissions follow the format resource:action where action can be:
read- View the resourcewrite- Create/update the resourcedelete- Delete the resource*- All actions (wildcard)
Special permissions:
*- Full access to everythingresource:*- All actions on a specific resource (e.g.,users:*)
Example permissions:
users:read- Can view usersusers:write- Can create/update usersusers:*- All user operationssettings:write- Can update settingsbilling:read- Can view billing information
License
MIT License - see LICENSE file for details.
Support
For support and questions:
- Documentation: https://docs.personql.com
- GitHub Issues: https://github.com/PersonQL/personql/issues
- Email: [email protected]
Usage
import { PersonQLProvider } from '@personql/react';
<PersonQLProvider apiKey="your-api-key">{/* your app */}</PersonQLProvider>;// All requests will use the gateway-worker URL automatically.
