npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@saas-support/react

v0.8.2

Published

Embeddable auth SDK for SaaS Support

Readme

@saas-support/react

Embeddable auth SDK for SaaS Support — drop-in sign-in, user menu, and settings components. Shadow DOM style isolation. Full theming support.

npm install @saas-support/react

Quick Start

import { SaaSProvider, SignIn, UserButton } from '@saas-support/react/react'

function App() {
  return (
    <SaaSProvider publishableKey="pub_live_..." baseUrl="https://api.example.com/v1">
      <UserButton />
    </SaaSProvider>
  )
}

Entry Points

| Import | Use | |--------|-----| | @saas-support/react | Vanilla JS client and types, no React dependency | | @saas-support/react/react | React components, hooks, provider |


Provider

Wrap your app in <SaaSProvider> to initialize the SDK:

import { SaaSProvider } from '@saas-support/react/react'

<SaaSProvider
  publishableKey="pub_live_..."
  baseUrl="https://api.saas-support.com/v1"
  appearance={{ baseTheme: 'dark' }}
>
  <App />
</SaaSProvider>

| Prop | Type | Required | Description | |------|------|----------|-------------| | publishableKey | string | No* | Publishable key for auth operations | | apiKey | string | No* | API key for server-side operations | | baseUrl | string | No | API base URL override | | appearance | Appearance | No | Global theme configuration |

* At least one of publishableKey or apiKey is required.


Components

<SignIn />

Combined sign-in and sign-up form with OAuth support, MFA, and a built-in mode toggle.

import { SignIn } from '@saas-support/react/react'

<SignIn
  initialMode="signIn"
  afterSignInUrl="/dashboard"
  afterSignUpUrl="/onboarding"
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | initialMode | 'signIn' \| 'signUp' | 'signIn' | Starting mode | | afterSignInUrl | string | — | Redirect URL after sign-in | | afterSignUpUrl | string | — | Redirect URL after sign-up | | appearance | Appearance | — | Theme overrides |

Features:

  • Email/password sign-in and sign-up
  • OAuth (Google, GitHub) when enabled in project settings
  • MFA verification (6-digit code)
  • Built-in toggle between sign-in and sign-up modes
  • Password validation against project settings

<UserButton />

Avatar button that opens a dropdown with org switcher, settings, and sign-out.

import { UserButton } from '@saas-support/react/react'

<UserButton
  showOrgSwitcher={true}
  afterSignOutUrl="/login"
  onOrgChange={(org) => console.log('Switched to', org.name)}
  onOrgSettingsClick={(org) => navigate(`/org/${org.slug}/settings`)}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | afterSignOutUrl | string | — | Redirect URL after sign-out | | afterDeleteAccountUrl | string | — | Redirect URL after account deletion | | showOrgSwitcher | boolean | true | Show org list in dropdown | | onOrgChange | (org: Org) => void | — | Called when user switches org | | onOrgSettingsClick | (org: Org) => void | — | Called when "Org settings" is clicked | | appearance | Appearance | — | Theme overrides |

Features:

  • Avatar with initials fallback
  • Invite notification badge
  • Inline org creation
  • Full settings panel (profile, organization, people, invites, billing)

<SettingsPanel />

Full-page settings overlay with tabs for profile, organization, people, invites, and billing. Typically opened by <UserButton> but can be used standalone.

import { SettingsPanel } from '@saas-support/react/react'

<SettingsPanel
  onClose={() => setShowSettings(false)}
  defaultTab="profile"
  afterDeleteAccountUrl="/login"
  onOrgDeleted={refreshOrgs}
  onOrgUpdated={refreshOrgs}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | onClose | () => void | required | Close callback | | defaultTab | SettingsTab | 'profile' | Initial active tab | | afterDeleteAccountUrl | string | — | Redirect after account deletion | | onOrgDeleted | () => void | — | Callback when org is deleted | | onOrgUpdated | () => void | — | Callback when org is updated |


Hooks

useAuth()

Primary auth state hook.

const { isLoaded, isSignedIn, user, signOut, getToken, refreshUser } = useAuth()

| Return | Type | Description | |--------|------|-------------| | isLoaded | boolean | SDK has finished loading | | isSignedIn | boolean | User is authenticated | | user | User \| null | Current user object | | signOut | () => Promise<void> | Sign out | | getToken | () => string | Get current access token | | refreshUser | () => Promise<User \| null> | Refresh user data from server |

useUser()

Lightweight user-only hook (no methods).

const { user, isLoaded } = useUser()

useSignIn()

Programmatic sign-in.

const { signIn, signInWithOAuth, submitMfaCode, isLoading, error, setError } = useSignIn()

const result = await signIn(email, password)
if (result && isMfaRequired(result)) {
  await submitMfaCode(result.mfaToken, code)
}

await signInWithOAuth('google') // or 'github'

useSignUp()

Programmatic sign-up.

const { signUp, isLoading, error, setError } = useSignUp()
await signUp(email, password)

useOrg()

Organization management.

const {
  orgs,              // Org[]
  selectedOrg,       // Org | null
  members,           // Member[]
  invites,           // PendingInvite[]
  selectOrg,         // (orgId: string) => Promise<void>
  createOrg,         // (name: string, slug: string) => Promise<Org | null>
  updateOrg,         // (orgId: string, params) => Promise<Org | null>
  deleteOrg,         // (orgId: string) => Promise<boolean>
  sendInvite,        // (orgId, email, role) => Promise<Invite | null>
  revokeInvite,      // (orgId, inviteId) => Promise<boolean>
  updateMemberRole,  // (orgId, userId, role) => Promise<boolean>
  removeMember,      // (orgId, userId) => Promise<boolean>
  refresh,
  isLoading,
  error,
} = useOrg()

useProfile()

Profile management with avatar upload.

const { user, updateProfile, uploadAvatar, changePassword, isLoading, error, success } = useProfile()

await updateProfile({ name: 'New Name' })
await uploadAvatar(imageBlob)
await changePassword(currentPassword, newPassword)

useInvites()

Pending invite notifications for the current user.

const { invites, accept, decline, refresh, isLoading, error } = useInvites()

await accept(inviteId)   // Accept org invitation
await decline(inviteId)  // Decline org invitation

useDeleteAccount()

Account deletion.

const { deleteAccount, isLoading, error } = useDeleteAccount()
await deleteAccount()

useSaaSContext()

Low-level context access.

const { client, user, isLoaded, appearance, settings } = useSaaSContext()

Vanilla JS (No React)

Use the client directly without React:

import { SaaSSupport } from '@saas-support/react'

const saas = new SaaSSupport({ publishableKey: 'pub_live_...' })

await saas.load()
const result = await saas.auth.signIn('[email protected]', 'password')
const user = await saas.auth.getUser()

saas.destroy()

AuthClient Methods

| Method | Returns | |--------|---------| | signIn(email, password) | Promise<AuthResult> | | signUp(email, password) | Promise<SignUpResult> | | signOut() | Promise<void> | | signInWithOAuth(provider) | Promise<SignInResult> | | submitMfaCode(mfaToken, code) | Promise<SignInResult> | | getToken() | Promise<string \| null> | | getUser() | Promise<User \| null> | | refreshUser() | Promise<User \| null> | | updateProfile(params) | Promise<User> | | uploadAvatar(imageBlob) | Promise<{ url }> | | changePassword(current, new) | Promise<void> | | deleteAccount() | Promise<void> | | getSettings() | Promise<ProjectSettings> | | listOrgs() | Promise<Org[]> | | getOrg(orgId) | Promise<Org> | | createOrg(name, slug) | Promise<Org> | | updateOrg(orgId, params) | Promise<Org> | | deleteOrg(orgId) | Promise<void> | | listMembers(orgId) | Promise<Member[]> | | updateMemberRole(orgId, userId, role) | Promise<void> | | removeMember(orgId, userId) | Promise<void> | | sendInvite(orgId, email, role) | Promise<Invite> | | listInvites(orgId) | Promise<PendingInvite[]> | | revokeInvite(orgId, inviteId) | Promise<void> | | listMyInvites() | Promise<MyPendingInvite[]> | | acceptInviteById(inviteId) | Promise<{ orgId, role }> | | declineInvite(inviteId) | Promise<void> |


Theming

All components render inside Shadow DOM for style isolation. Customize via the appearance prop:

<SaaSProvider
  publishableKey="pub_live_..."
  appearance={{
    baseTheme: 'dark',
    variables: {
      colorPrimary: '#8b5cf6',
      colorBackground: '#0f172a',
      colorText: '#f1f5f9',
      fontFamily: '"Inter", sans-serif',
      borderRadius: '12px',
    },
    elements: {
      card: { boxShadow: '0 4px 24px rgba(0,0,0,0.3)' },
      submitButton: { fontWeight: 700 },
    },
    fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
  }}
>

Set fontUrl: null to disable CDN font loading.

Theme Variables

| Variable | Light Default | Dark Default | |----------|--------------|--------------| | colorPrimary | #6366f1 | #818cf8 | | colorBackground | #ffffff | #1e1e2e | | colorText | #1a1a2e | #e2e8f0 | | colorInputBackground | #f8f9fa | #2a2a3e | | colorInputBorder | #e2e8f0 | #3a3a4e | | colorError | #ef4444 | #f87171 | | colorSuccess | #22c55e | #4ade80 | | fontFamily | -apple-system, ... | -apple-system, ... | | borderRadius | 8px | 8px |


Error Handling

All errors are thrown as SaaSError instances:

import { SaaSError } from '@saas-support/react'

try {
  await saas.auth.signIn(email, password)
} catch (err) {
  if (err instanceof SaaSError) {
    console.log(err.code)           // 401
    console.log(err.domain)         // 'auth'
    console.log(err.isUnauthorized) // true
  }
}

Token Storage

  • Access token: stored in memory (XSS-safe)
  • Refresh token: stored in localStorage as ss_rt_<first12chars>
  • Token refresh is automatic and transparent

License

MIT