JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 30
  • Score
    100M100P100Q118054F
  • License MIT

React components for Spry apps dropdown and profile menu with dynamic API integration - Google-style UI

Package Exports

  • spry-apps-dropdown

Readme

spry-apps-dropdown

A React component library for Spry apps dropdown and profile menu with multi-account switching. Features a Google-style UI with beautiful animations and automatic Keycloak authentication.

Features

  • 🎨 Beautiful Material-UI design with smooth animations (Google-inspired)
  • 👥 Multi-account switching - Users can log into multiple accounts and switch between them
  • 🔄 Automatic data fetching from API with caching
  • 🔐 Built-in Keycloak/OIDC authentication via react-oidc-context
  • ⚡ Loading and error states
  • 📱 Responsive layout
  • 🎯 TypeScript support
  • 🔌 Easy integration - Just 3 steps!

Installation

npm install spry-apps-dropdown react-oidc-context oidc-client-ts

Peer Dependencies:

  • react ^18.0.0 || ^19.0.0
  • react-dom ^18.0.0 || ^19.0.0
  • react-oidc-context ^3.3.0
  • oidc-client-ts ^1.0.0

Quick Start (3 Steps)

Step 1: Wrap Your App with SpryAuthProvider

// main.tsx or index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { SpryAuthProvider } from 'spry-apps-dropdown'
import App from './App'

const oidcConfig = {
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
  client_id: 'your-client-id',
  redirect_uri: window.location.origin,
  post_logout_redirect_uri: window.location.origin,
  onSigninCallback: () => {
    window.history.replaceState({}, document.title, window.location.pathname)
  },
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <SpryAuthProvider config={oidcConfig}>
      <App />
    </SpryAuthProvider>
  </React.StrictMode>
)

Step 2: Use useSpryAccountManager Hook

// App.tsx
import { useSpryAccountManager } from 'spry-apps-dropdown'

function App() {
  const accountManager = useSpryAccountManager()

  return (
    <div>
      {/* Your app content */}
    </div>
  )
}

Step 3: Add the TopBar Component

// App.tsx
import { TopBar, useSpryAuth, useSpryAccountManager } from 'spry-apps-dropdown'

function App() {
  const auth = useSpryAuth()
  const accountManager = useSpryAccountManager()

  return (
    <div>
      <AppBar>
        <Toolbar>
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            My App
          </Typography>

          <TopBar
            apiUrl="https://sprylogin.com/apps-api"
            onSignOut={() => auth.signoutRedirect()}
            getAuthToken={() => auth.user?.access_token || null}
            accountManager={accountManager}
          />
        </Toolbar>
      </AppBar>

      {/* Your app content */}
    </div>
  )
}

That's it! Your app now has:

  • ✅ Multi-account switching
  • ✅ Apps dropdown
  • ✅ Profile menu
  • ✅ Automatic authentication

Using Auth Token in Your API Calls

Get the current user's token using useSpryAuth():

import { useSpryAuth } from 'spry-apps-dropdown'

function MyComponent() {
  const auth = useSpryAuth()

  useEffect(() => {
    const token = auth.user?.access_token

    fetch('/api/data', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    })
  }, [auth.user])
}

For React Query:

import { useSpryAuth } from 'spry-apps-dropdown'
import { useQuery } from '@tanstack/react-query'

function MyComponent() {
  const auth = useSpryAuth()

  const { data } = useQuery({
    queryKey: ['data'],
    queryFn: async () => {
      const token = auth.user?.access_token
      const res = await fetch('/api/data', {
        headers: { 'Authorization': `Bearer ${token}` }
      })
      return res.json()
    },
    enabled: !!auth.user,
  })
}

Multi-Account Switching

The package automatically handles multi-account switching:

  1. User clicks "Add Another Account" in ProfileMenu
  2. Redirects to Keycloak for login (with prompt=login)
  3. New account is added to localStorage
  4. User can switch between accounts from ProfileMenu
  5. API calls automatically use the active account's token

No manual code needed - it just works!


API Reference

TopBar Component

Combined apps dropdown + profile menu (recommended).

Props

Prop Type Required Default Description
apiUrl string Yes - Base URL for apps API
accountManager UseAccountManagerReturn Yes - Account manager from useSpryAccountManager()
profileApiUrl string No Same as apiUrl Base URL for profile API
onSignOut () => void No - Callback when user signs out
getAuthToken () => string | null No - Function to get auth token
profileHeaders Record<string, string> No - Custom headers for profile API
showAppsDropdown boolean No true Show apps dropdown
showProfileMenu boolean No true Show profile menu
appsRefetchInterval number No 300000 (5 min) Apps refetch interval (ms)
appsCacheTime number No 300000 (5 min) Apps cache duration (ms)
profileRefetchInterval number No 300000 (5 min) Profile refetch interval (ms)
profileCacheTime number No 300000 (5 min) Profile cache duration (ms)

SpryAuthProvider Component

Wraps your app with Keycloak/OIDC authentication.

Props

Prop Type Required Description
config UserManagerSettings Yes OIDC configuration object
children ReactNode Yes Your app components

Example config:

const oidcConfig = {
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
  client_id: 'my-app-client',
  redirect_uri: window.location.origin,
  post_logout_redirect_uri: window.location.origin,
  onSigninCallback: () => {
    window.history.replaceState({}, document.title, window.location.pathname)
  },
}

useSpryAccountManager() Hook

Returns account manager for multi-account switching.

Returns

{
  accounts: StoredAccount[]        // All logged-in accounts
  activeAccount: StoredAccount     // Currently active account
  switchAccount: (id: string) => Promise<void>
  addNewAccount: () => Promise<void>
  removeAccount: (id: string) => Promise<void>
  refreshAccountToken: (id: string) => Promise<void>
}

useSpryAuth() Hook

Returns the current authentication state (alias for useAuth() from react-oidc-context).

Returns

{
  user: User | null               // Current user object
  isAuthenticated: boolean        // Is user logged in
  isLoading: boolean             // Is auth loading
  signinRedirect: () => Promise<void>
  signoutRedirect: () => Promise<void>
  // ... other react-oidc-context methods
}

Advanced: Handling Account Switches

If you're using React Query and want to refetch data when accounts switch:

import { useSpryAccountManager } from 'spry-apps-dropdown'
import { useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'

function useMultiAccountManager() {
  const queryClient = useQueryClient()
  const accountManager = useSpryAccountManager()

  // Listen for account switches and invalidate queries
  useEffect(() => {
    const handleAccountSwitch = () => {
      queryClient.invalidateQueries() // Refetch all data
    }

    window.addEventListener('auth:account-switched', handleAccountSwitch)
    return () => window.removeEventListener('auth:account-switched', handleAccountSwitch)
  }, [queryClient])

  return accountManager
}

Backend API Requirements

Your backend should expose these endpoints:

GET /api/apps

Returns list of apps for the dropdown.

{
  "apps": [
    {
      "id": "abc123",
      "name": "SpryBoard",
      "description": "Collaborative whiteboard",
      "url": "https://board.spry.com",
      "iconUrl": "https://example.com/icon.png",
      "color": "#4285f4",
      "order": 1,
      "createdAt": "2026-02-13T10:00:00.000Z",
      "updatedAt": "2026-02-13T10:00:00.000Z"
    }
  ],
  "lastUpdated": "2026-02-13T10:00:00.000Z"
}

GET /api/profile

Returns user profile information (requires Bearer token).

GET /api/profile
Authorization: Bearer <jwt-token>

Response:

{
  "email": "user@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "avatar": "https://example.com/avatar.jpg",
  "manageAccountUrl": "https://account.spry.com"
}

Customization

Custom Theme

import { ThemeProvider, createTheme } from '@mui/material/styles'

const theme = createTheme({
  palette: {
    primary: {
      main: '#1a73e8',
    },
  },
})

<ThemeProvider theme={theme}>
  <SpryAuthProvider config={oidcConfig}>
    <App />
  </SpryAuthProvider>
</ThemeProvider>

Hide Apps Dropdown or Profile Menu

<TopBar
  apiUrl="https://sprylogin.com/apps-api"
  showAppsDropdown={false}  // Hide apps dropdown
  showProfileMenu={true}     // Show only profile menu
  accountManager={accountManager}
/>

Troubleshooting

"User not authenticated" error

Make sure you wrapped your app with SpryAuthProvider:

<SpryAuthProvider config={oidcConfig}>
  <App />
</SpryAuthProvider>

Account switching doesn't work

Pass the accountManager prop to TopBar:

const accountManager = useSpryAccountManager()

<TopBar accountManager={accountManager} {...otherProps} />

API calls use wrong token after switching

Use useSpryAuth() to get the current token:

const auth = useSpryAuth()
const token = auth.user?.access_token

TypeScript Support

Full TypeScript support included. Types are automatically available:

import type {
  UseAccountManagerReturn,
  StoredAccount,
  OIDCUser,
  TopBarProps
} from 'spry-apps-dropdown'

Browser Support

  • Modern browsers with ES2020 support
  • Requires localStorage API

License

MIT



Support

For issues or questions:

  • Open an issue on GitHub
  • Check INTEGRATION.md for detailed guides