JSPM

  • Created
  • Published
  • Downloads 136
  • Score
    100M100P100Q76424F
  • License MIT

A SDK for Buildbase

Package Exports

  • @buildbase/sdk
  • @buildbase/sdk/dist/saas-os.css

Readme

@buildbase/sdk

A React SDK for BuildBase that provides essential components to build SaaS applications faster. Skip the plumbing and focus on your core product with built-in authentication, workspace management, and user management.

πŸ“‘ Table of Contents

πŸš€ Features

  • πŸ” Authentication System - Complete auth flow with sign-in/sign-out
  • 🏒 Workspace Management - Multi-workspace support with switching capabilities
  • πŸ‘₯ Role-Based Access Control - User roles and workspace-specific permissions
  • 🎯 Feature Flags - Workspace-level and user-level feature toggles
  • πŸ‘€ User Management - User attributes and feature flags management
  • πŸ“ Beta Form - Pre-built signup/waitlist form component
  • πŸ“‘ Event System - Subscribe to user and workspace events
  • πŸ›‘οΈ Error Handling - Centralized error handling with error boundaries

πŸ“¦ Installation

npm install @buildbase/sdk

Peer Dependencies

This package requires React 19 and React DOM 19:

npm install react@^19.0.0 react-dom@^19.0.0

πŸ—οΈ Quick Start

1. Import CSS

First, import the required CSS file in your app:

import '@buildbase/sdk/dist/saas-os.css';

2. Create Client Provider

Create a client-side provider component:

'use client';

import { SaaSOSProvider } from '@buildbase/sdk';
import React from 'react';

export default function SaaSProvider(props: { children: React.ReactNode }) {
  return (
    <SaaSOSProvider
      serverUrl="https://your-api-server.com"
      version="v1"
      orgId="your-org-id"
      auth={{
        clientId: 'your-client-id',
        redirectUrl: 'http://localhost:3000',
        callbacks: {
          handleAuthentication: async (code: string) => {
            // Exchange OAuth code for session ID
            const response = await fetch('/api/auth/token', {
              method: 'POST',
              body: JSON.stringify({ code }),
            });
            const data = await response.json();
            // Return sessionId - SDK will use this for authenticated requests
            return { sessionId: data.sessionId };
          },
          onSignOut: async () => {
            // Clean up any custom tokens/storage on sign out
            localStorage.removeItem('custom_token');
          },
          handleEvent: async (eventType, data) => {
            // Handle SDK events (user created, workspace changed, etc.)
            console.log('SDK Event:', eventType, data);
          },
          onWorkspaceChange: async ({ workspace, user, role }) => {
            // Called before switching workspace (e.g. generate token). Used on "Switch to" and page refresh/restore.
            // Switch proceeds only when this resolves; reject to abort.
            console.log('Switching to workspace:', workspace.name, 'as', role);
          },
        },
      }}
    >
      {props.children}
    </SaaSOSProvider>
  );
}

3. Wrap Your App

Use the provider in your app layout:

import SaaSProvider from './components/SaaSProvider';

function App() {
  return (
    <SaaSProvider>
      <YourAppContent />
    </SaaSProvider>
  );
}

export default App;

4. Workspace Management

The WorkspaceSwitcher component uses a render prop pattern, giving you full control over the UI. Configure onWorkspaceChange in auth.callbacks (SaaSOSProvider) to handle workspace switchesβ€”used when clicking "Switch to" and when restoring from storage on page refresh. The callback receives { workspace, user, role } so you don't need to look up the user's role:

import React from 'react';
import { WorkspaceSwitcher } from '@buildbase/sdk';

function WorkspaceExample() {
  return (
    <WorkspaceSwitcher
      trigger={(isLoading, currentWorkspace) => {
        if (isLoading) {
          return <div>Loading...</div>;
        }

        if (!currentWorkspace) {
          return (
            <div className="flex items-center gap-2 min-w-40 border rounded-md p-2 hover:bg-muted cursor-pointer">
              <div className="bg-gray-200 flex aspect-square size-8 items-center justify-center rounded-lg"></div>
              <div className="grid flex-1 text-left text-sm leading-tight">Choose a workspace</div>
            </div>
          );
        }

        return (
          <div className="flex items-center gap-2 min-w-40 border rounded-md p-2 hover:bg-muted cursor-pointer">
            <div className="flex items-center justify-center h-full w-full bg-muted rounded-lg max-h-8 max-w-8">
              {currentWorkspace?.image && (
                <img src={currentWorkspace?.image} alt={currentWorkspace?.name} />
              )}
            </div>
            <div className="grid flex-1 text-left text-sm leading-tight">
              <span className="truncate font-medium">{currentWorkspace?.name}</span>
            </div>
          </div>
        );
      }}
    />
  );
}

πŸ” Authentication

Authentication Hook

Use the useSaaSAuth hook to manage authentication state and actions:

import { useSaaSAuth } from '@buildbase/sdk';

function AuthExample() {
  const { user, isAuthenticated, signIn, signOut, status } = useSaaSAuth();

  return (
    <div>
      {!isAuthenticated ? (
        <div>
          <h1>Welcome! Please sign in</h1>
          <button onClick={signIn} disabled={status === 'loading'}>
            {status === 'loading' ? 'Signing in...' : 'Sign In'}
          </button>
        </div>
      ) : (
        <div>
          <h1>Welcome back, {user?.name}!</h1>
          <p>Email: {user?.email}</p>
          <p>Role: {user?.role}</p>
          <button onClick={signOut}>Sign Out</button>
        </div>
      )}
    </div>
  );
}

Authentication Hook Properties

const {
  user, // Current user object (null if not authenticated)
  session, // Full session object with user and sessionId
  isAuthenticated, // Boolean: true if user is authenticated
  isLoading, // Boolean: true when checking authentication status
  isRedirecting, // Boolean: true when redirecting for OAuth
  status, // AuthStatus: 'loading' | 'redirecting' | 'authenticating' | 'authenticated' | 'unauthenticated' (use AuthStatus enum for type-safe checks)
  signIn, // Function: initiates sign-in flow
  signOut, // Function: signs out the user
  openWorkspaceSettings, // Function: opens workspace settings dialog
} = useSaaSAuth();

Authentication Components

For declarative rendering, use the conditional components:

import { WhenAuthenticated, WhenUnauthenticated } from '@buildbase/sdk';

function App() {
  return (
    <div>
      <WhenUnauthenticated>
        <LoginPage />
      </WhenUnauthenticated>

      <WhenAuthenticated>
        <Dashboard />
      </WhenAuthenticated>
    </div>
  );
}

πŸ‘₯ Role-Based Access Control

Role Components

Control access based on user roles:

import { WhenRoles, WhenWorkspaceRoles } from '@buildbase/sdk';

function AdminPanel() {
  return (
    <div>
      {/* Global user roles */}
      <WhenRoles roles={['admin', 'super-admin']}>
        <AdminControls />
      </WhenRoles>

      {/* Workspace-specific roles */}
      <WhenWorkspaceRoles roles={['owner', 'admin']}>
        <WorkspaceSettings />
      </WhenWorkspaceRoles>

      {/* With fallback content */}
      <WhenRoles roles={['admin']} fallback={<p>You need admin access to view this content</p>}>
        <SensitiveData />
      </WhenRoles>
    </div>
  );
}

πŸŽ›οΈ Feature Flags

Control feature visibility based on workspace and user settings:

import {
  WhenWorkspaceFeatureEnabled,
  WhenWorkspaceFeatureDisabled,
  WhenUserFeatureEnabled,
  WhenUserFeatureDisabled,
} from '@buildbase/sdk';

function FeatureExample() {
  return (
    <div>
      {/* Workspace-level features */}
      <WhenWorkspaceFeatureEnabled slug="advanced-analytics">
        <AdvancedAnalytics />
      </WhenWorkspaceFeatureEnabled>

      <WhenWorkspaceFeatureDisabled slug="beta-features">
        <p>Beta features are not enabled for this workspace</p>
      </WhenWorkspaceFeatureDisabled>

      {/* User-level features */}
      <WhenUserFeatureEnabled slug="premium-features">
        <PremiumDashboard />
      </WhenUserFeatureEnabled>

      <WhenUserFeatureDisabled slug="trial-mode">
        <UpgradePrompt />
      </WhenUserFeatureDisabled>
    </div>
  );
}

Feature Flags Hook

Use the useUserFeatures hook to check feature flags programmatically:

import { useUserFeatures } from '@buildbase/sdk';

function FeatureCheck() {
  const { features, isFeatureEnabled, refreshFeatures } = useUserFeatures();

  return (
    <div>{isFeatureEnabled('premium-features') ? <PremiumContent /> : <StandardContent />}</div>
  );
}

πŸ‘€ User Management

User Attributes

Manage custom user attributes (key-value pairs):

import { useUserAttributes } from '@buildbase/sdk';

function UserProfile() {
  const { attributes, isLoading, updateAttribute, updateAttributes, refreshAttributes } =
    useUserAttributes();

  const handleUpdate = async () => {
    // Update single attribute
    await updateAttribute('theme', 'dark');

    // Or update multiple attributes
    await updateAttributes({
      theme: 'dark',
      notifications: true,
      language: 'en',
    });
  };

  return (
    <div>
      <p>Theme: {attributes.theme}</p>
      <button onClick={handleUpdate}>Update Preferences</button>
    </div>
  );
}

🏒 Complete Workspace Management

The useSaaSWorkspaces hook provides comprehensive workspace management:

import { useSaaSWorkspaces } from '@buildbase/sdk';

function WorkspaceManager() {
  const {
    workspaces, // Array of all workspaces
    currentWorkspace, // Currently selected workspace
    loading, // Loading state
    refreshing, // Refreshing state
    switching, // True when a workspace switch is in progress
    error, // Error message
    fetchWorkspaces, // Fetch all workspaces
    refreshWorkspaces, // Background refresh
    setCurrentWorkspace, // Direct workspace set (bypasses onWorkspaceChange)
    switchToWorkspace, // Full switch flow: onWorkspaceChange first, then set workspace
    createWorkspace, // Create new workspace
    updateWorkspace, // Update workspace
    deleteWorkspace, // Delete workspace
    getUsers, // Get workspace users
    addUser, // Add user to workspace
    removeUser, // Remove user from workspace
    updateUser, // Update user role/permissions
    getFeatures, // Get all available features
    updateFeature, // Toggle workspace feature
    getProfile, // Get current user profile
    updateUserProfile, // Update user profile
  } = useSaaSWorkspaces();

  // Example: Create a workspace
  const handleCreate = async () => {
    await createWorkspace('My Workspace', 'https://example.com/logo.png');
  };

  // Example: Add user to workspace
  const handleAddUser = async () => {
    await addUser(currentWorkspace._id, 'user@example.com', 'member');
  };

  return <div>{/* Your workspace UI */}</div>;
}

πŸ’° Public Pricing (No Login)

Display subscription plans and pricing on public pages (e.g. marketing site, pricing page) without requiring users to log in.

usePublicPlans

Fetches public plans by slug. Returns items (features, limits, quotas) and plans (with pricing). You construct the layout from this data:

import { usePublicPlans } from '@buildbase/sdk';

function PublicPricingPage() {
  const { items, plans, loading, error } = usePublicPlans('main-pricing');

  if (loading) return <Loading />;
  if (error) return <Error message={error} />;

  return (
    <div>
      {plans.map(plan => (
        <PlanCard key={plan._id} plan={plan} items={items} />
      ))}
    </div>
  );
}

PricingPage Component

Use the PricingPage component with a render-prop pattern:

import { PricingPage } from '@buildbase/sdk';

function PublicPricingPage() {
  return (
    <PricingPage slug="main-pricing">
      {({ loading, error, items, plans, refetch }) => {
        if (loading) return <Loading />;
        if (error) return <Error message={error} />;

        return (
          <div>
            {plans.map(plan => (
              <PlanCard key={plan._id} plan={plan} items={items} />
            ))}
          </div>
        );
      }}
    </PricingPage>
  );
}
Prop Type Description
slug string Plan group slug (e.g. 'main-pricing', 'enterprise')
children (details) => ReactNode Render prop receiving { loading, error, items, plans, refetch }
loadingFallback ReactNode Custom loading UI (defaults to skeleton)
errorFallback (error: string) => ReactNode Custom error UI

Response shape: items = subscription item definitions (features, limits, quotas with category); plans = plan versions with pricing, quotas, features, limits.

Backend requirement: GET /api/v1/public/{orgId}/plans/{groupSlug} must be implemented and allow unauthenticated access.

πŸ“ Beta Form Component

Use the pre-built BetaForm component for signup/waitlist forms:

import { BetaForm } from '@buildbase/sdk';

function SignupPage() {
  return (
    <BetaForm
      onSuccess={() => console.log('Form submitted!')}
      onError={error => console.error(error)}
      language="en" // Optional: 'en' | 'es' | 'fr' | 'de' | 'zh' | 'ja' | 'ko'
      showSuccessMessage={true}
      hideLogo={false}
      hideTitles={false}
    />
  );
}

πŸ“‘ Event System

Subscribe to SDK events for user and workspace changes:

import { SaaSOSProvider, eventEmitter } from '@buildbase/sdk';

// In your provider configuration
<SaaSOSProvider
  auth={{
    callbacks: {
      handleEvent: async (eventType, data) => {
        switch (eventType) {
          case 'user:created':
            console.log('User created:', data.user);
            break;
          case 'workspace:changed':
            console.log('Workspace changed:', data.workspace);
            break;
          case 'workspace:user-added':
            console.log('User added to workspace:', data.userId);
            break;
          // ... handle other events
        }
      },
    },
  }}
>
  {children}
</SaaSOSProvider>;

Available Events

  • user:created - User account created
  • user:updated - User profile updated
  • workspace:changed - Workspace switched (fires after switch completes; use onWorkspaceChange for prep before switch)
  • workspace:created - New workspace created
  • workspace:updated - Workspace updated
  • workspace:deleted - Workspace deleted
  • workspace:user-added - User added to workspace
  • workspace:user-removed - User removed from workspace
  • workspace:user-role-changed - User role changed

πŸ›‘οΈ Error Handling

The SDK provides comprehensive error handling:

import { ErrorBoundary, SDKError, handleError, errorHandler } from '@buildbase/sdk';

// Wrap your app with ErrorBoundary
function App() {
  return (
    <ErrorBoundary>
      <YourApp />
    </ErrorBoundary>
  );
}

// Configure error handler
errorHandler.configure({
  enableConsoleLogging: true,
  showUserNotifications: false,
  onError: (error, context) => {
    // Custom error handling
    console.error('SDK Error:', error, context);
  },
});

// Use handleError in your code
try {
  // Some operation
} catch (error) {
  handleError(error, {
    component: 'MyComponent',
    action: 'handleSubmit',
    metadata: { userId: '123' },
  });
}

βš™οΈ Settings

Access OS-level settings:

import { useSaaSSettings } from '@buildbase/sdk';

function SettingsExample() {
  const { settings, getSettings } = useSaaSSettings();

  return (
    <div>
      <p>Max Workspaces: {settings?.workspace.maxWorkspaces}</p>
    </div>
  );
}

πŸ“š API Reference

Enums

  • ApiVersion - API version enum (currently only V1)
  • AuthStatus - Auth status enum: loading | redirecting | authenticating | authenticated | unauthenticated. Use with useSaaSAuth().status; isLoading, isAuthenticated, and isRedirecting are derived from it.

Types

All TypeScript types are exported for type safety. See the TypeScript definitions for complete type information.

βš™οΈ Configuration Reference

SaaSOSProvider Props

Prop Type Required Description
serverUrl string βœ… API server URL (must be valid URL)
version ApiVersion βœ… API version (currently only 'v1')
orgId string βœ… Organization ID (must be valid MongoDB ObjectId - 24 hex characters)
auth IAuthConfig ❌ Authentication configuration
children ReactNode βœ… React children

Auth Configuration

interface IAuthConfig {
  clientId: string; // OAuth client ID
  redirectUrl: string; // OAuth redirect URL
  callbacks?: {
    handleAuthentication: (code: string) => Promise<{ sessionId: string }>;
    onSignOut?: () => Promise<void>;
    handleEvent?: (eventType: EventType, data: EventData) => void | Promise<void>;
    onWorkspaceChange?: (params: OnWorkspaceChangeParams) => Promise<void>;
  };
}

interface OnWorkspaceChangeParams {
  workspace: IWorkspace;
  user: AuthUser | null;
  role: string | null; // User's role in this workspace
}

Validation Requirements

  • serverUrl: Must be a valid URL (e.g., https://api.example.com)
  • version: Must be exactly 'v1' (only supported version)
  • orgId: Must be a valid MongoDB ObjectId (24 hexadecimal characters, e.g., 507f1f77bcf86cd799439011)

BetaForm Props

Prop Type Default Description
onSuccess () => void - Callback when form submits successfully
onError (error: string) => void - Callback when form submission fails
className string 'w-full' CSS class for form container
fieldClassName string 'flex flex-col gap-1.5 w-full' CSS class for form fields
language 'en' | 'es' | 'fr' | 'de' | 'zh' | 'ja' | 'ko' Auto-detect Form language
customTexts Partial<FormText> {} Custom text overrides
autoFocus boolean true Auto-focus name field
showSuccessMessage boolean true Show success message after submit
successMessageDuration number - Duration to show success message (ms)
hideLogo boolean false Hide logo
hideTitles boolean false Hide titles

🎯 Common Patterns

Pattern 1: Protected Routes

import { WhenAuthenticated, WhenUnauthenticated } from '@buildbase/sdk';

function App() {
  return (
    <WhenUnauthenticated>
      <LoginPage />
    </WhenUnauthenticated>

    <WhenAuthenticated>
      <ProtectedRoutes />
    </WhenAuthenticated>
  );
}

function ProtectedRoutes() {
  return (
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/settings" element={<Settings />} />
    </Routes>
  );
}

Pattern 2: Role-Based Navigation

import { WhenRoles } from '@buildbase/sdk';

function Navigation() {
  return (
    <nav>
      <Link to="/dashboard">Dashboard</Link>
      <Link to="/projects">Projects</Link>

      <WhenRoles roles={['admin', 'owner']}>
        <Link to="/admin">Admin Panel</Link>
      </WhenRoles>

      <WhenRoles roles={['admin']}>
        <Link to="/settings">Settings</Link>
      </WhenRoles>
    </nav>
  );
}

Pattern 3: Workspace Context Provider

import { useSaaSWorkspaces } from '@buildbase/sdk';
import { createContext, useContext } from 'react';

const WorkspaceContext = createContext(null);

export function WorkspaceProvider({ children }) {
  const workspaceData = useSaaSWorkspaces();

  return <WorkspaceContext.Provider value={workspaceData}>{children}</WorkspaceContext.Provider>;
}

export function useWorkspace() {
  return useContext(WorkspaceContext);
}

Pattern 4: Feature Gated Components

import { WhenWorkspaceFeatureEnabled } from '@buildbase/sdk';

function Dashboard() {
  return (
    <div>
      <StandardFeatures />

      <WhenWorkspaceFeatureEnabled slug="advanced-analytics">
        <AdvancedAnalytics />
      </WhenWorkspaceFeatureEnabled>

      <WhenWorkspaceFeatureEnabled slug="ai-assistant">
        <AIAssistant />
      </WhenWorkspaceFeatureEnabled>
    </div>
  );
}

Pattern 5: Handling Workspace Changes

import { useSaaSWorkspaces } from '@buildbase/sdk';
import { useEffect } from 'react';

function App() {
  const { currentWorkspace, switching } = useSaaSWorkspaces();

  useEffect(() => {
    if (currentWorkspace) {
      // Update your app state when workspace changes
      console.log('Workspace changed:', currentWorkspace);
      // Reload data, update context, etc.
    }
  }, [currentWorkspace]);

  // Show loading during switch (switchingToId !== null)
  if (switching) return <LoadingOverlay />;

  return <YourApp />;
}

For token generation or prep before switching, configure onWorkspaceChange in auth callbacks (see Quick Start)β€”it receives { workspace, user, role }.

Pattern 6: Error Boundary with Custom Fallback

import { ErrorBoundary, SDKError } from '@buildbase/sdk';

function CustomErrorFallback({ error, resetError }) {
  if (error instanceof SDKError) {
    return (
      <div>
        <h2>SDK Error: {error.message}</h2>
        <button onClick={resetError}>Try Again</button>
      </div>
    );
  }

  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={resetError}>Reload</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary fallback={CustomErrorFallback}>
      <YourApp />
    </ErrorBoundary>
  );
}

πŸ”§ Troubleshooting

Common Issues

1. "Invalid orgId" Error

Problem: orgId must be a valid MongoDB ObjectId (24 hexadecimal characters).

Solution:

// ❌ Wrong
orgId = '123';

// βœ… Correct
orgId = '507f1f77bcf86cd799439011'; // 24 hex characters

2. "Invalid serverUrl" Error

Problem: serverUrl must be a valid URL.

Solution:

// ❌ Wrong
serverUrl = 'api.example.com';
serverUrl = 'not-a-url';

// βœ… Correct
serverUrl = 'https://api.example.com';
serverUrl = 'http://localhost:3000';

3. Authentication Not Working

Problem: User can't sign in or session not persisting.

Solutions:

  • Ensure handleAuthentication callback returns { sessionId: string }
  • Check that your backend API is correctly exchanging the OAuth code
  • Verify redirectUrl matches your OAuth app configuration
  • Check browser console for error messages
// βœ… Correct callback
handleAuthentication: async (code: string) => {
  const response = await fetch('/api/auth/token', {
    method: 'POST',
    body: JSON.stringify({ code }),
  });
  const data = await response.json();
  return { sessionId: data.sessionId }; // Must return sessionId
};

4. Workspace Not Loading

Problem: Workspaces array is empty or not updating.

Solutions:

  • Ensure user is authenticated before fetching workspaces
  • Call fetchWorkspaces() explicitly if needed
  • Check network tab for API errors
  • Verify user has access to workspaces
const { fetchWorkspaces, workspaces } = useSaaSWorkspaces();

useEffect(() => {
  if (isAuthenticated) {
    fetchWorkspaces(); // Explicitly fetch if needed
  }
}, [isAuthenticated]);

5. Feature Flags Not Working

Problem: Feature flags always return false or undefined.

Solutions:

  • Ensure getFeatures() is called (usually automatic)
  • Check that feature slugs match your backend configuration
  • Verify workspace has the feature enabled
  • Check user has the feature enabled (for user-level features)
const { getFeatures, currentWorkspace } = useSaaSWorkspaces();

useEffect(() => {
  getFeatures(); // Ensure features are loaded
}, []);

6. TypeScript Errors

Problem: Type errors when using the SDK.

Solutions:

  • Ensure you're using React 19+ (check peer dependencies)

  • Import types explicitly if needed:

    import type { IWorkspace, IUser } from '@buildbase/sdk';
  • Check that all required props are provided

7. CSS Not Loading

Problem: Components look unstyled.

Solution: Ensure CSS is imported:

import '@buildbase/sdk/dist/saas-os.css';

FAQ

Q: Can I use this with Next.js?
A: Yes! Just ensure you use 'use client' directive in components using SDK hooks.

Q: Can I use this with other React frameworks?
A: Yes, as long as you're using React 19+.

Q: How do I customize the workspace switcher UI?
A: Use the trigger render prop to fully customize the UI.

Q: Can I use multiple workspaces simultaneously?
A: No, the SDK manages one current workspace at a time. Use switchToWorkspace() (runs onWorkspaceChange first) or setCurrentWorkspace() (direct set, bypasses callback).

Q: How do I handle offline scenarios?
A: The SDK stores session data in localStorage. Handle offline scenarios in your handleAuthentication callback.

Q: Can I use this without TypeScript?
A: Yes, but TypeScript is recommended for better developer experience.

πŸ’‘ Best Practices

1. Provider Setup

βœ… Do: Wrap your entire app with SaaSOSProvider at the root level.

// βœ… Good
function App() {
  return (
    <SaaSOSProvider {...config}>
      <YourApp />
    </SaaSOSProvider>
  );
}

❌ Don't: Nest providers or use multiple instances.

// ❌ Bad
<SaaSOSProvider>
  <SaaSOSProvider>
    {' '}
    {/* Don't nest */}
    <App />
  </SaaSOSProvider>
</SaaSOSProvider>

2. Error Handling

βœ… Do: Wrap your app with ErrorBoundary and configure error handler.

// βœ… Good
<ErrorBoundary>
  <SaaSOSProvider {...config}>
    <App />
  </SaaSOSProvider>
</ErrorBoundary>

βœ… Do: Use handleError for custom error handling.

try {
  await someOperation();
} catch (error) {
  handleError(error, {
    component: 'MyComponent',
    action: 'operation',
  });
}

3. Workspace Management

βœ… Do: Use useSaaSWorkspaces hook for workspace operations.

// βœ… Good
const { currentWorkspace, switchToWorkspace, switching } = useSaaSWorkspaces();
// switchToWorkspace: runs onWorkspaceChange first (token gen, etc.)
// switching: true when switch is in progress

βœ… Do: Configure onWorkspaceChange in auth callbacks for token generationβ€”receives { workspace, user, role }.

❌ Don't: Manually manage workspace state.

// ❌ Bad
const [workspace, setWorkspace] = useState(null); // Don't do this

4. Feature Flags

βœ… Do: Use feature flag components for conditional rendering.

// βœ… Good
<WhenWorkspaceFeatureEnabled slug="feature">
  <FeatureComponent />
</WhenWorkspaceFeatureEnabled>

βœ… Do: Check features programmatically when needed.

// βœ… Good
const { isFeatureEnabled } = useUserFeatures();
if (isFeatureEnabled('premium')) {
  // Do something
}

5. Authentication

βœ… Do: Use WhenAuthenticated/WhenUnauthenticated for route protection.

// βœ… Good
<WhenAuthenticated>
  <ProtectedRoute />
</WhenAuthenticated>

βœ… Do: Handle authentication errors gracefully.

// βœ… Good
const { signIn, status } = useSaaSAuth();
<button onClick={signIn} disabled={status === 'loading'}>
  {status === 'loading' ? 'Signing in...' : 'Sign In'}
</button>;

6. Event Handling

βœ… Do: Handle events in your provider configuration. Use onWorkspaceChange for prep before switch (e.g. generate token), and handleEvent for post-switch notifications.

// βœ… Good
<SaaSOSProvider
  auth={{
    callbacks: {
      onWorkspaceChange: async ({ workspace, user, role }) => {
        await generateTokenForWorkspace(workspace._id, user?.id, role);
      },
      handleEvent: async (eventType, data) => {
        if (eventType === 'workspace:changed') {
          // Workspace already switched; update app state
        }
      },
    },
  }}
>

7. TypeScript

βœ… Do: Use TypeScript for better type safety.

// βœ… Good
import type { IWorkspace, IUser } from '@buildbase/sdk';

function MyComponent({ workspace }: { workspace: IWorkspace }) {
  // Type-safe code
}

8. Performance

βœ… Do: Memoize expensive computations.

// βœ… Good
const filteredWorkspaces = useMemo(() => workspaces.filter(w => w.active), [workspaces]);

βœ… Do: Use refreshWorkspaces() for background updates instead of fetchWorkspaces().

// βœ… Good - doesn't block UI
refreshWorkspaces();

// Use fetchWorkspaces() only when you need to wait for the result
await fetchWorkspaces();

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ†˜ Support


Made with ❀️ by the BuildBase team