spry-apps-dropdown 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,71 +1,17 @@
1
1
  # spry-apps-dropdown
2
- ## Integration Guide
3
2
 
4
- ### 1. OIDC/Keycloak Setup
5
-
6
- This package requires OIDC authentication. Use `react-oidc-context` or `@react-keycloak/web` for best results.
7
-
8
- **Example OIDC config:**
9
-
10
- ```tsx
11
- const oidcConfig = {
12
- authority: 'https://auth.sprylogin.com/realms/sprylogin',
13
- client_id: 'my-app-client',
14
- redirect_uri: window.location.origin,
15
- post_logout_redirect_uri: window.location.origin,
16
- onSigninCallback: () => {
17
- window.history.replaceState({}, document.title, window.location.pathname)
18
- },
19
- }
20
- ```
21
-
22
- ### 2. Multi-Account Management
23
-
24
- The package supports multi-account switching, add-account, and soft logout. All account state is stored in localStorage for cross-app/iframe sync.
25
-
26
- **Syncing across apps/iframe:**
27
- - Use the same localStorage key (`spry_accounts`) in all apps.
28
- - When an account is added, removed, or switched, update localStorage and reload state in all apps.
29
- - For iframe/bridge integration, use a shared localStorage or a custom syncBridge client.
30
-
31
- ### 3. Add Another Account Flow
32
-
33
- When adding another account:
34
- - A flag (`spry_add_account_pending`) is set in localStorage.
35
- - After soft logout, the app checks this flag and triggers OIDC login for the new account.
36
- - On callback, the new account is added and set as active.
37
-
38
- ### 4. Cross-App Sync
39
-
40
- To sync active account and account removal across apps:
41
- - Listen for changes to `spry_accounts` in localStorage (using the `storage` event or polling).
42
- - When the accounts state changes, update your app's state and UI.
43
- - If using an iframe bridge, ensure the bridge propagates account changes to all connected apps.
44
-
45
- ### 5. Troubleshooting
46
-
47
- - If new accounts are not added after login, ensure your OIDC provider updates the user context before the callback runs.
48
- - If cross-app sync is not working, check that all apps use the same localStorage key and listen for changes.
49
- - For CORS or redirect issues, verify your OIDC config and Keycloak setup.
50
-
51
- ### 6. Example Integration
52
-
53
- See the Quick Start and Usage sections below for full code examples.
54
-
55
-
56
- A React component library for displaying Spry apps dropdown and profile menu with dynamic API integration. Features a Google-style UI with beautiful animations.
3
+ 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.
57
4
 
58
5
  ## Features
59
6
 
60
7
  - 🎨 Beautiful Material-UI design with smooth animations (Google-inspired)
61
- - 🔄 Automatic data fetching from API
62
- - 💾 Built-in caching and refetching
8
+ - 👥 **Multi-account switching** - Users can log into multiple accounts and switch between them
9
+ - 🔄 Automatic data fetching from API with caching
10
+ - 🔐 Built-in Keycloak/OIDC authentication via react-oidc-context
63
11
  - ⚡ Loading and error states
64
12
  - 📱 Responsive layout
65
13
  - 🎯 TypeScript support
66
- - 🔌 Easy integration
67
- - 👤 Profile menu with avatar, account management, and sign out (always shows Manage your Account)
68
- - 🎛️ Combined TopBar component with both apps dropdown and profile menu
14
+ - 🔌 Easy integration - Just 3 steps!
69
15
 
70
16
  ## Installation
71
17
 
@@ -73,18 +19,28 @@ A React component library for displaying Spry apps dropdown and profile menu wit
73
19
  npm install spry-apps-dropdown react-oidc-context oidc-client-ts
74
20
  ```
75
21
 
76
- This package expects `react`, `react-dom`, `react-oidc-context`, and `oidc-client-ts` to be provided by the consuming app.
22
+ **Peer Dependencies:**
23
+ - `react` ^18.0.0 || ^19.0.0
24
+ - `react-dom` ^18.0.0 || ^19.0.0
25
+ - `react-oidc-context` ^3.3.0
26
+ - `oidc-client-ts` ^1.0.0
27
+
28
+ ---
77
29
 
78
- ## React OIDC Quick Start (Lowest Effort)
30
+ ## Quick Start (3 Steps)
31
+
32
+ ### Step 1: Wrap Your App with `SpryAuthProvider`
79
33
 
80
34
  ```tsx
35
+ // main.tsx or index.tsx
81
36
  import React from 'react'
82
37
  import ReactDOM from 'react-dom/client'
83
- import { SpryAuthProvider, TopBar, useSpryAccountManager } from 'spry-apps-dropdown'
38
+ import { SpryAuthProvider } from 'spry-apps-dropdown'
39
+ import App from './App'
84
40
 
85
41
  const oidcConfig = {
86
42
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
87
- client_id: 'my-app-client',
43
+ client_id: 'your-client-id',
88
44
  redirect_uri: window.location.origin,
89
45
  post_logout_redirect_uri: window.location.origin,
90
46
  onSigninCallback: () => {
@@ -92,17 +48,6 @@ const oidcConfig = {
92
48
  },
93
49
  }
94
50
 
95
- function App() {
96
- const accountManager = useSpryAccountManager()
97
-
98
- return (
99
- <TopBar
100
- apiUrl="https://your-api.com"
101
- accountManager={accountManager}
102
- />
103
- )
104
- }
105
-
106
51
  ReactDOM.createRoot(document.getElementById('root')!).render(
107
52
  <React.StrictMode>
108
53
  <SpryAuthProvider config={oidcConfig}>
@@ -112,253 +57,252 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
112
57
  )
113
58
  ```
114
59
 
115
- ## Usage
116
-
117
- ### TopBar Component (Easiest - Recommended for v2.0+)
118
-
119
- The `TopBar` component combines both apps dropdown and profile menu in a single component, just like Google's interface:
60
+ ### Step 2: Use `useSpryAccountManager` Hook
120
61
 
121
62
  ```tsx
122
- import { TopBar } from 'spry-apps-dropdown'
63
+ // App.tsx
64
+ import { useSpryAccountManager } from 'spry-apps-dropdown'
65
+
66
+ function App() {
67
+ const accountManager = useSpryAccountManager()
123
68
 
124
- function MyAppBar() {
125
69
  return (
126
- <AppBar>
127
- <Toolbar>
128
- <Typography variant="h6" sx={{ flexGrow: 1 }}>
129
- My App
130
- </Typography>
131
-
132
- <TopBar
133
- apiUrl="https://your-api.com"
134
- onSignOut={() => handleLogout()}
135
- getAuthToken={() => yourAuthToken}
136
- appsRefetchInterval={30000} // 30 seconds
137
- appsCacheTime={30000}
138
- profileRefetchInterval={5 * 60 * 1000} // 5 minutes
139
- profileCacheTime={5 * 60 * 1000}
140
- />
141
- </Toolbar>
142
- </AppBar>
70
+ <div>
71
+ {/* Your app content */}
72
+ </div>
143
73
  )
144
74
  }
145
75
  ```
146
76
 
147
- ### Profile Menu Only
148
-
149
- Use just the profile menu component:
77
+ ### Step 3: Add the TopBar Component
150
78
 
151
79
  ```tsx
152
- import { ProfileMenuConnected } from 'spry-apps-dropdown'
80
+ // App.tsx
81
+ import { TopBar, useSpryAuth, useSpryAccountManager } from 'spry-apps-dropdown'
82
+
83
+ function App() {
84
+ const auth = useSpryAuth()
85
+ const accountManager = useSpryAccountManager()
153
86
 
154
- function MyComponent() {
155
87
  return (
156
- <ProfileMenuConnected
157
- apiUrl="https://your-api.com"
158
- onSignOut={() => handleLogout()}
159
- getAuthToken={() => yourAuthToken}
160
- refetchInterval={5 * 60 * 1000} // 5 minutes
161
- cacheTime={5 * 60 * 1000}
162
- />
88
+ <div>
89
+ <AppBar>
90
+ <Toolbar>
91
+ <Typography variant="h6" sx={{ flexGrow: 1 }}>
92
+ My App
93
+ </Typography>
94
+
95
+ <TopBar
96
+ apiUrl="https://sprylogin.com/apps-api"
97
+ onSignOut={() => auth.signoutRedirect()}
98
+ getAuthToken={() => auth.user?.access_token || null}
99
+ accountManager={accountManager}
100
+ />
101
+ </Toolbar>
102
+ </AppBar>
103
+
104
+ {/* Your app content */}
105
+ </div>
163
106
  )
164
107
  }
165
108
  ```
166
109
 
167
- ### Apps Dropdown Only (Basic Usage)
110
+ **That's it!** Your app now has:
111
+ - ✅ Multi-account switching
112
+ - ✅ Apps dropdown
113
+ - ✅ Profile menu
114
+ - ✅ Automatic authentication
168
115
 
169
- Import `AppsDropdownConnected` for automatic API integration:
116
+ ---
117
+
118
+ ## Using Auth Token in Your API Calls
119
+
120
+ Get the current user's token using `useSpryAuth()`:
170
121
 
171
122
  ```tsx
172
- import { useState, useRef } from 'react'
173
- import { IconButton } from '@mui/material'
174
- import { Apps } from '@mui/icons-material'
175
- import { AppsDropdownConnected } from 'spry-apps-dropdown'
123
+ import { useSpryAuth } from 'spry-apps-dropdown'
176
124
 
177
125
  function MyComponent() {
178
- const [appsMenuOpen, setAppsMenuOpen] = useState(false)
179
- const appsButtonRef = useRef<HTMLButtonElement>(null)
126
+ const auth = useSpryAuth()
180
127
 
181
- return (
182
- <>
183
- <IconButton
184
- ref={appsButtonRef}
185
- onClick={() => setAppsMenuOpen(!appsMenuOpen)}
186
- >
187
- <Apps />
188
- </IconButton>
189
-
190
- <AppsDropdownConnected
191
- apiUrl="http://localhost:3002"
192
- open={appsMenuOpen}
193
- onClose={() => setAppsMenuOpen(false)}
194
- buttonRef={appsButtonRef}
195
- />
196
- </>
197
- )
128
+ useEffect(() => {
129
+ const token = auth.user?.access_token
130
+
131
+ fetch('/api/data', {
132
+ headers: {
133
+ 'Authorization': `Bearer ${token}`
134
+ }
135
+ })
136
+ }, [auth.user])
198
137
  }
199
138
  ```
200
139
 
201
- ### Advanced Usage
202
-
203
- Use `AppsDropdown` directly with custom data source:
140
+ **For React Query:**
204
141
 
205
142
  ```tsx
206
- import { AppsDropdown, useAppsData } from 'spry-apps-dropdown'
143
+ import { useSpryAuth } from 'spry-apps-dropdown'
144
+ import { useQuery } from '@tanstack/react-query'
207
145
 
208
146
  function MyComponent() {
209
- const [appsMenuOpen, setAppsMenuOpen] = useState(false)
210
- const appsButtonRef = useRef<HTMLButtonElement>(null)
211
-
212
- // Use the hook for more control
213
- const { apps, isLoading, error, refetch } = useAppsData('http://localhost:3002', {
214
- refetchInterval: 10 * 60 * 1000, // 10 minutes
215
- cacheTime: 10 * 60 * 1000 // 10 minutes
147
+ const auth = useSpryAuth()
148
+
149
+ const { data } = useQuery({
150
+ queryKey: ['data'],
151
+ queryFn: async () => {
152
+ const token = auth.user?.access_token
153
+ const res = await fetch('/api/data', {
154
+ headers: { 'Authorization': `Bearer ${token}` }
155
+ })
156
+ return res.json()
157
+ },
158
+ enabled: !!auth.user,
216
159
  })
217
-
218
- return (
219
- <>
220
- <IconButton ref={appsButtonRef} onClick={() => setAppsMenuOpen(!appsMenuOpen)}>
221
- <Apps />
222
- </IconButton>
223
-
224
- <AppsDropdown
225
- apps={apps}
226
- open={appsMenuOpen}
227
- onClose={() => setAppsMenuOpen(false)}
228
- buttonRef={appsButtonRef}
229
- isLoading={isLoading}
230
- error={error}
231
- onRetry={refetch}
232
- />
233
- </>
234
- )
235
160
  }
236
161
  ```
237
162
 
163
+ ---
164
+
165
+ ## Multi-Account Switching
166
+
167
+ The package automatically handles multi-account switching:
168
+
169
+ 1. **User clicks "Add Another Account"** in ProfileMenu
170
+ 2. **Redirects to Keycloak** for login (with `prompt=login`)
171
+ 3. **New account is added** to localStorage
172
+ 4. **User can switch** between accounts from ProfileMenu
173
+ 5. **API calls automatically use** the active account's token
174
+
175
+ **No manual code needed** - it just works!
176
+
177
+ ---
178
+
238
179
  ## API Reference
239
180
 
240
- ### `TopBar` (New in v2.0)
181
+ ### `TopBar` Component
241
182
 
242
- Combined component with both apps dropdown and profile menu, positioned side by side like Google's interface.
183
+ Combined apps dropdown + profile menu (recommended).
243
184
 
244
185
  #### Props
245
186
 
246
187
  | Prop | Type | Required | Default | Description |
247
188
  |------|------|----------|---------|-------------|
248
189
  | `apiUrl` | `string` | Yes | - | Base URL for apps API |
190
+ | `accountManager` | `UseAccountManagerReturn` | Yes | - | Account manager from `useSpryAccountManager()` |
249
191
  | `profileApiUrl` | `string` | No | Same as `apiUrl` | Base URL for profile API |
250
192
  | `onSignOut` | `() => void` | No | - | Callback when user signs out |
251
- | `getAuthToken` | `() => string \| null \| Promise<string \| null>` | No | - | Function to get auth token |
193
+ | `getAuthToken` | `() => string \| null` | No | - | Function to get auth token |
252
194
  | `profileHeaders` | `Record<string, string>` | No | - | Custom headers for profile API |
253
195
  | `showAppsDropdown` | `boolean` | No | `true` | Show apps dropdown |
254
196
  | `showProfileMenu` | `boolean` | No | `true` | Show profile menu |
255
- | `appsRefetchInterval` | `number` | No | `300000` (5 min) | Apps refetch interval in ms |
256
- | `appsCacheTime` | `number` | No | `300000` (5 min) | Apps cache duration in ms |
257
- | `profileRefetchInterval` | `number` | No | `300000` (5 min) | Profile refetch interval in ms |
258
- | `profileCacheTime` | `number` | No | `300000` (5 min) | Profile cache duration in ms |
259
-
260
- ### `ProfileMenuConnected` (New in v2.0)
261
-
262
- Profile menu component with automatic API integration.
263
-
264
- #### Props
265
-
266
- | Prop | Type | Required | Default | Description |
267
- |------|------|----------|---------|-------------|
268
- | `apiUrl` | `string` | Yes | - | Base URL for profile API |
269
- | `onSignOut` | `() => void` | No | - | Callback when user signs out |
270
- | `onManageAccount` | `() => void` | No | - | Callback for Manage your Account button. If not provided, it redirects to `https://sprylogin.com/my-account/` |
271
- | `getAuthToken` | `() => string \| null \| Promise<string \| null>` | No | - | Function to get auth token |
272
- | `headers` | `Record<string, string>` | No | - | Custom headers for API requests |
273
- | `refetchInterval` | `number` | No | `300000` (5 min) | Auto-refetch interval in ms |
274
- | `cacheTime` | `number` | No | `300000` (5 min) | Cache duration in ms |
197
+ | `appsRefetchInterval` | `number` | No | `300000` (5 min) | Apps refetch interval (ms) |
198
+ | `appsCacheTime` | `number` | No | `300000` (5 min) | Apps cache duration (ms) |
199
+ | `profileRefetchInterval` | `number` | No | `300000` (5 min) | Profile refetch interval (ms) |
200
+ | `profileCacheTime` | `number` | No | `300000` (5 min) | Profile cache duration (ms) |
275
201
 
276
- ### Manage Account behavior
202
+ ---
277
203
 
278
- The Manage your Account button is always shown. It uses this order:
204
+ ### `SpryAuthProvider` Component
279
205
 
280
- 1. `onManageAccount` callback (if provided)
281
- 2. `profile.manageAccountUrl` (if present)
282
- 3. Default: `https://sprylogin.com/my-account/`
283
-
284
- ### `AppsDropdownConnected`
285
-
286
- The main component with automatic API integration.
206
+ Wraps your app with Keycloak/OIDC authentication.
287
207
 
288
208
  #### Props
289
209
 
290
- | Prop | Type | Required | Default | Description |
291
- |------|------|----------|---------|-------------|
292
- | `apiUrl` | `string` | Yes | - | Base URL of the Spry Apps API |
293
- | `open` | `boolean` | Yes | - | Whether the dropdown is open |
294
- | `onClose` | `() => void` | Yes | - | Callback when dropdown closes |
295
- | `buttonRef` | `React.RefObject<HTMLButtonElement>` | No | - | Ref for positioning |
296
- | `refetchInterval` | `number` | No | `300000` (5 min) | Auto-refetch interval in ms |
297
- | `cacheTime` | `number` | No | `300000` (5 min) | Cache duration in ms |
298
-
299
- ### `AppsDropdown`
210
+ | Prop | Type | Required | Description |
211
+ |------|------|----------|-------------|
212
+ | `config` | `UserManagerSettings` | Yes | OIDC configuration object |
213
+ | `children` | `ReactNode` | Yes | Your app components |
300
214
 
301
- The presentational component for custom implementations.
215
+ **Example config:**
302
216
 
303
- #### Props
217
+ ```tsx
218
+ const oidcConfig = {
219
+ authority: 'https://auth.sprylogin.com/realms/sprylogin',
220
+ client_id: 'my-app-client',
221
+ redirect_uri: window.location.origin,
222
+ post_logout_redirect_uri: window.location.origin,
223
+ onSigninCallback: () => {
224
+ window.history.replaceState({}, document.title, window.location.pathname)
225
+ },
226
+ }
227
+ ```
304
228
 
305
- | Prop | Type | Required | Default | Description |
306
- |------|------|----------|---------|-------------|
307
- | `apps` | `App[]` | Yes | - | Array of apps to display |
308
- | `open` | `boolean` | Yes | - | Whether the dropdown is open |
309
- | `onClose` | `() => void` | Yes | - | Callback when dropdown closes |
310
- | `buttonRef` | `React.RefObject<HTMLButtonElement>` | No | - | Ref for positioning |
311
- | `isLoading` | `boolean` | No | `false` | Loading state |
312
- | `error` | `string \| null` | No | `null` | Error message |
313
- | `onRetry` | `() => void` | No | - | Retry callback |
229
+ ---
314
230
 
315
- ### `useAppsData`
231
+ ### `useSpryAccountManager()` Hook
316
232
 
317
- Hook for fetching apps data.
233
+ Returns account manager for multi-account switching.
318
234
 
319
- #### Parameters
235
+ #### Returns
320
236
 
321
237
  ```typescript
322
- useAppsData(apiUrl: string, options?: {
323
- refetchInterval?: number // Default: 300000 (5 minutes)
324
- cacheTime?: number // Default: 300000 (5 minutes)
325
- })
238
+ {
239
+ accounts: StoredAccount[] // All logged-in accounts
240
+ activeAccount: StoredAccount // Currently active account
241
+ switchAccount: (id: string) => Promise<void>
242
+ addNewAccount: () => Promise<void>
243
+ removeAccount: (id: string) => Promise<void>
244
+ refreshAccountToken: (id: string) => Promise<void>
245
+ }
326
246
  ```
327
247
 
248
+ ---
249
+
250
+ ### `useSpryAuth()` Hook
251
+
252
+ Returns the current authentication state (alias for `useAuth()` from react-oidc-context).
253
+
328
254
  #### Returns
329
255
 
330
256
  ```typescript
331
257
  {
332
- apps: App[] // Array of apps
333
- isLoading: boolean // Loading state
334
- error: string | null // Error message if any
335
- refetch: () => Promise<void> // Manual refetch function
258
+ user: User | null // Current user object
259
+ isAuthenticated: boolean // Is user logged in
260
+ isLoading: boolean // Is auth loading
261
+ signinRedirect: () => Promise<void>
262
+ signoutRedirect: () => Promise<void>
263
+ // ... other react-oidc-context methods
336
264
  }
337
265
  ```
338
266
 
339
- ### Types
267
+ ---
340
268
 
341
- ```typescript
342
- interface App {
343
- id: string
344
- name: string
345
- description: string
346
- url: string
347
- iconUrl: string
348
- color: string
349
- order: number
350
- createdAt: string
351
- updatedAt: string
269
+ ## Advanced: Handling Account Switches
270
+
271
+ If you're using React Query and want to refetch data when accounts switch:
272
+
273
+ ```tsx
274
+ import { useSpryAccountManager } from 'spry-apps-dropdown'
275
+ import { useQueryClient } from '@tanstack/react-query'
276
+ import { useEffect } from 'react'
277
+
278
+ function useMultiAccountManager() {
279
+ const queryClient = useQueryClient()
280
+ const accountManager = useSpryAccountManager()
281
+
282
+ // Listen for account switches and invalidate queries
283
+ useEffect(() => {
284
+ const handleAccountSwitch = () => {
285
+ queryClient.invalidateQueries() // Refetch all data
286
+ }
287
+
288
+ window.addEventListener('auth:account-switched', handleAccountSwitch)
289
+ return () => window.removeEventListener('auth:account-switched', handleAccountSwitch)
290
+ }, [queryClient])
291
+
292
+ return accountManager
352
293
  }
353
294
  ```
354
295
 
296
+ ---
297
+
355
298
  ## Backend API Requirements
356
299
 
357
- The component expects the following API endpoint structure:
300
+ Your backend should expose these endpoints:
358
301
 
359
302
  ### `GET /api/apps`
360
303
 
361
- Returns:
304
+ Returns list of apps for the dropdown.
305
+
362
306
  ```json
363
307
  {
364
308
  "apps": [
@@ -378,26 +322,17 @@ Returns:
378
322
  }
379
323
  ```
380
324
 
381
- ### `GET /api/profile` (New in v2.0)
325
+ ### `GET /api/profile`
382
326
 
383
- Returns user profile information. Supports multiple authentication methods:
327
+ Returns user profile information (requires Bearer token).
384
328
 
385
- **Option 1: JWT Token (via Authorization header)**
386
329
  ```http
387
330
  GET /api/profile
388
331
  Authorization: Bearer <jwt-token>
389
332
  ```
390
333
 
391
- **Option 2: Custom Headers**
392
- ```http
393
- GET /api/profile
394
- X-User-Email: user@example.com
395
- X-User-FirstName: John
396
- X-User-LastName: Doe
397
- X-User-Avatar: https://example.com/avatar.jpg
398
- ```
399
-
400
334
  **Response:**
335
+
401
336
  ```json
402
337
  {
403
338
  "email": "user@example.com",
@@ -408,52 +343,113 @@ X-User-Avatar: https://example.com/avatar.jpg
408
343
  }
409
344
  ```
410
345
 
411
- See [spry-apps-server](https://github.com/your-org/spry-apps-server) for the reference backend implementation.
346
+ ---
412
347
 
413
- ## Styling
348
+ ## Customization
414
349
 
415
- The component uses Material-UI components. You can customize the appearance using the MUI theme:
350
+ ### Custom Theme
416
351
 
417
352
  ```tsx
418
353
  import { ThemeProvider, createTheme } from '@mui/material/styles'
419
354
 
420
355
  const theme = createTheme({
421
- // Your custom theme
356
+ palette: {
357
+ primary: {
358
+ main: '#1a73e8',
359
+ },
360
+ },
422
361
  })
423
362
 
424
- function App() {
425
- return (
426
- <ThemeProvider theme={theme}>
427
- <AppsDropdownConnected {...props} />
428
- </ThemeProvider>
429
- )
430
- }
363
+ <ThemeProvider theme={theme}>
364
+ <SpryAuthProvider config={oidcConfig}>
365
+ <App />
366
+ </SpryAuthProvider>
367
+ </ThemeProvider>
431
368
  ```
432
369
 
433
- ## Caching
370
+ ### Hide Apps Dropdown or Profile Menu
371
+
372
+ ```tsx
373
+ <TopBar
374
+ apiUrl="https://sprylogin.com/apps-api"
375
+ showAppsDropdown={false} // Hide apps dropdown
376
+ showProfileMenu={true} // Show only profile menu
377
+ accountManager={accountManager}
378
+ />
379
+ ```
434
380
 
435
- The component automatically caches apps data in `localStorage` to provide instant loading on subsequent visits. The cache respects the `cacheTime` option.
381
+ ---
436
382
 
437
- ## Error Handling
383
+ ## Troubleshooting
438
384
 
439
- - Network errors are caught and displayed with a retry button
440
- - Cached data is used as fallback when API is unavailable
441
- - Loading spinner shown during initial fetch
385
+ ### "User not authenticated" error
386
+
387
+ Make sure you wrapped your app with `SpryAuthProvider`:
388
+
389
+ ```tsx
390
+ <SpryAuthProvider config={oidcConfig}>
391
+ <App />
392
+ </SpryAuthProvider>
393
+ ```
394
+
395
+ ### Account switching doesn't work
396
+
397
+ Pass the `accountManager` prop to `TopBar`:
398
+
399
+ ```tsx
400
+ const accountManager = useSpryAccountManager()
401
+
402
+ <TopBar accountManager={accountManager} {...otherProps} />
403
+ ```
404
+
405
+ ### API calls use wrong token after switching
406
+
407
+ Use `useSpryAuth()` to get the current token:
408
+
409
+ ```tsx
410
+ const auth = useSpryAuth()
411
+ const token = auth.user?.access_token
412
+ ```
413
+
414
+ ---
415
+
416
+ ## TypeScript Support
417
+
418
+ Full TypeScript support included. Types are automatically available:
419
+
420
+ ```tsx
421
+ import type {
422
+ UseAccountManagerReturn,
423
+ StoredAccount,
424
+ OIDCUser,
425
+ TopBarProps
426
+ } from 'spry-apps-dropdown'
427
+ ```
428
+
429
+ ---
442
430
 
443
431
  ## Browser Support
444
432
 
445
433
  - Modern browsers with ES2020 support
446
434
  - Requires localStorage API
447
435
 
436
+ ---
437
+
448
438
  ## License
449
439
 
450
440
  MIT
451
441
 
452
- ## Contributing
442
+ ---
453
443
 
454
- Contributions are welcome! Please open an issue or submit a pull request.
455
-
456
- ## Related
444
+ ## Related Documentation
457
445
 
446
+ - [INTEGRATION.md](./INTEGRATION.md) - Detailed multi-account integration guide
458
447
  - [Spry Apps Server](https://github.com/your-org/spry-apps-server) - Backend API server
459
- - [Spry Account](https://github.com/your-org/spry-account) - Reference implementation
448
+
449
+ ---
450
+
451
+ ## Support
452
+
453
+ For issues or questions:
454
+ - Open an issue on GitHub
455
+ - Check INTEGRATION.md for detailed guides